From 34e47599f25ea8896dc559c1a7344b767e72c93a Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 24 Oct 2023 10:57:35 +0400 Subject: [PATCH 01/55] abstract --- src/qibo/transpiler/abstract.py | 97 +++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/qibo/transpiler/abstract.py diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py new file mode 100644 index 0000000000..f717010da5 --- /dev/null +++ b/src/qibo/transpiler/abstract.py @@ -0,0 +1,97 @@ +from abc import ABC, abstractmethod +from typing import Tuple + +import networkx as nx +from qibo import gates +from qibo.config import raise_error +from qibo.models import Circuit + +from qibolab.native import NativeType + + +def find_gates_qubits_pairs(circuit: Circuit): + """Translate qibo circuit into a list of pairs of qubits to be used by the router and placer. + + Args: + circuit (qibo.models.Circuit): circuit to be transpiled. + + Returns: + translated_circuit (list): list containing qubits targeted by two qubit gates + """ + translated_circuit = [] + for gate in circuit.queue: + if isinstance(gate, gates.M): + pass + elif len(gate.qubits) == 2: + translated_circuit.append(sorted(gate.qubits)) + elif len(gate.qubits) >= 3: + raise_error(ValueError, "Gates targeting more than 2 qubits are not supported") + return translated_circuit + + +class Placer(ABC): + @abstractmethod + 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 + + Args: + circuit (qibo.models.Circuit): circuit to be mapped. + + Returns: + initial_layout (dict): dictionary containing the initial logical to physical qubit mapping. + """ + + +class Router(ABC): + @abstractmethod + 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]: + """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. + + Returns: + matched_circuit (qibo.models.Circuit): routed circuit + final_layout (dict): dictionary containing the final logical to physical qubit mapping. + """ + + +class Optimizer(ABC): + """An optimizer tries to reduce the number of gates during transpilation.""" + + @abstractmethod + def __call__(self, circuit: Circuit, *args) -> Circuit: + """Find initial qubit mapping + + Args: + circuit (qibo.models.Circuit): circuit to be optimized + + Returns: + optimized_circuit (qibo.models.Circuit): circuit with optimized number of gates. + """ + + +class Unroller(ABC): + @abstractmethod + def __init__(self, native_gates: NativeType, *args): + """An unroller decomposes gates into native gates.""" + + @abstractmethod + def __call__(self, circuit: Circuit, *args) -> Circuit: + """Find initial qubit mapping + + Args: + circuit (qibo.models.Circuit): circuit to be optimized + + Returns: + translated_circuit (qibo.models.Circuit): circuit with native gates. + """ From 23318dac1ca272c13953d1186b81d3124731bb57 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 06:58:23 +0000 Subject: [PATCH 02/55] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/transpiler/abstract.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py index f717010da5..b0638e8fd6 100644 --- a/src/qibo/transpiler/abstract.py +++ b/src/qibo/transpiler/abstract.py @@ -2,12 +2,12 @@ from typing import Tuple import networkx as nx +from qibolab.native import NativeType + from qibo import gates from qibo.config import raise_error from qibo.models import Circuit -from qibolab.native import NativeType - def find_gates_qubits_pairs(circuit: Circuit): """Translate qibo circuit into a list of pairs of qubits to be used by the router and placer. @@ -25,7 +25,9 @@ def find_gates_qubits_pairs(circuit: Circuit): elif len(gate.qubits) == 2: translated_circuit.append(sorted(gate.qubits)) elif len(gate.qubits) >= 3: - raise_error(ValueError, "Gates targeting more than 2 qubits are not supported") + raise_error( + ValueError, "Gates targeting more than 2 qubits are not supported" + ) return translated_circuit @@ -52,7 +54,9 @@ 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, initial_layout: dict, *args + ) -> Tuple[Circuit, dict]: """Match circuit to hardware connectivity. Args: From af9fdedd79a2fc4e57b73e358dddb18ade3b00f6 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 30 Oct 2023 12:13:52 +0400 Subject: [PATCH 03/55] modify dependencies --- src/qibo/transpiler/__init__.py | 6 + src/qibo/transpiler/blocks.py | 297 ++++++++ src/qibo/transpiler/optimizer.py | 50 ++ src/qibo/transpiler/pipeline.py | 216 ++++++ src/qibo/transpiler/placer.py | 317 +++++++++ src/qibo/transpiler/router.py | 658 ++++++++++++++++++ src/qibo/transpiler/star_connectivity.py | 164 +++++ src/qibo/transpiler/unitary_decompositions.py | 253 +++++++ src/qibo/transpiler/unroller.py | 461 ++++++++++++ tests/test_transpilers_abstract.py | 23 + tests/test_transpilers_blocks.py | 295 ++++++++ tests/test_transpilers_optimizer.py | 50 ++ tests/test_transpilers_pipeline.py | 243 +++++++ tests/test_transpilers_placer.py | 201 ++++++ tests/test_transpilers_router.py | 329 +++++++++ tests/test_transpilers_star_connectivity.py | 90 +++ ...test_transpilers_unitary_decompositions.py | 189 +++++ tests/test_transpilers_unroller.py | 233 +++++++ 18 files changed, 4075 insertions(+) create mode 100644 src/qibo/transpiler/__init__.py create mode 100644 src/qibo/transpiler/blocks.py create mode 100644 src/qibo/transpiler/optimizer.py create mode 100644 src/qibo/transpiler/pipeline.py create mode 100644 src/qibo/transpiler/placer.py create mode 100644 src/qibo/transpiler/router.py create mode 100644 src/qibo/transpiler/star_connectivity.py create mode 100644 src/qibo/transpiler/unitary_decompositions.py create mode 100644 src/qibo/transpiler/unroller.py create mode 100644 tests/test_transpilers_abstract.py create mode 100644 tests/test_transpilers_blocks.py create mode 100644 tests/test_transpilers_optimizer.py create mode 100644 tests/test_transpilers_pipeline.py create mode 100644 tests/test_transpilers_placer.py create mode 100644 tests/test_transpilers_router.py create mode 100644 tests/test_transpilers_star_connectivity.py create mode 100644 tests/test_transpilers_unitary_decompositions.py create mode 100644 tests/test_transpilers_unroller.py diff --git a/src/qibo/transpiler/__init__.py b/src/qibo/transpiler/__init__.py new file mode 100644 index 0000000000..3ed3779d69 --- /dev/null +++ b/src/qibo/transpiler/__init__.py @@ -0,0 +1,6 @@ +from qibo.transpiler.optimizer import Preprocessing, Rearrange +from qibo.transpiler.pipeline import Passes +from qibo.transpiler.placer import Custom, Random, ReverseTraversal, Subgraph, Trivial +from qibo.transpiler.router import Sabre, ShortestPaths +from qibo.transpiler.star_connectivity import StarConnectivity +from qibo.transpiler.unroller import NativeGates diff --git a/src/qibo/transpiler/blocks.py b/src/qibo/transpiler/blocks.py new file mode 100644 index 0000000000..b3be3eaf66 --- /dev/null +++ b/src/qibo/transpiler/blocks.py @@ -0,0 +1,297 @@ +from typing import Optional, Union + +from qibo import Circuit +from qibo.gates import Gate + + +class BlockingError(Exception): + """Raise when an error occurs in the blocking procedure""" + + +class Block: + """A block contains a subset of gates acting on two qubits. + + Args: + qubits (tuple): qubits where the block is acting. + gates (list): list of gates that compose the block. + name (str or int): name of the block. + + Properties: + entangled (bool): True if the block entangles the qubits (there is at least one two qubit gate). + """ + + def __init__( + self, qubits: tuple, gates: list, name: Optional[Union[str, int]] = None + ): + self.qubits = qubits + self.gates = gates + self.name = name + + @property + def entangled(self): + return self.count_2q_gates() > 0 + + def rename(self, name): + """Rename block""" + self.name = name + + def add_gate(self, gate: Gate): + """Add a new gate to the block.""" + if not set(gate.qubits).issubset(self.qubits): + raise BlockingError( + "Gate acting on qubits {} can't be added to block acting on qubits {}.".format( + gate.qubits, self._qubits + ) + ) + self.gates.append(gate) + + def count_2q_gates(self): + """Return the number of two qubit gates in the block.""" + return count_2q_gates(self.gates) + + @property + def qubits(self): + """Return a sorted tuple with qubits of the block.""" + return tuple(sorted(self._qubits)) + + @qubits.setter + def qubits(self, qubits): + self._qubits = qubits + + def fuse(self, block: "Block", name: str = None): + """Fuse the current block with a new one, the qubits they are acting on must coincide. + + Args: + block (:class:`qibolab.transpilers.blocks.Block`): block to fuse. + name (str): name of the fused block. + + Return: + fused_block (:class:`qibolab.transpilers.blocks.Block`): fusion of the two input blocks. + """ + if not self.qubits == block.qubits: + raise BlockingError( + "In order to fuse two blocks their qubits must coincide." + ) + return Block(qubits=self.qubits, gates=self.gates + block.gates, name=name) + + def on_qubits(self, new_qubits): + """Return a new block acting on the new qubits. + + Args: + new_qubits (tuple): new qubits where the block is acting. + """ + qubits_dict = dict(zip(self.qubits, new_qubits)) + new_gates = [gate.on_qubits(qubits_dict) for gate in self.gates] + return Block(qubits=new_qubits, gates=new_gates, name=self.name) + + # TODO: use real QM properties to check commutation + def commute(self, block: "Block"): + """Check if a block commutes with the current one. + + Args: + block (:class:`qibolab.transpilers.blocks.Block`): block to check commutation. + + Return: + True if the two blocks don't share any qubit. + False otherwise. + """ + if len(set(self.qubits).intersection(block.qubits)) > 0: + return False + return True + + # TODO + def kak_decompose(self): # pragma: no cover + """Return KAK decomposition of the block. + This should be done only if the block is entangled and the number of + two qubit gates is higher than the number after the decomposition. + """ + raise NotImplementedError + + +class CircuitBlocks: + """A CircuitBlocks contains a quantum circuit decomposed in two qubits blocks. + + Args: + circuit (qibo.models.Circuit): circuit to be decomposed. + index_names (bool): assign names to the blocks + """ + + def __init__(self, circuit: Circuit, index_names: bool = False): + self.block_list = block_decomposition(circuit) + self._index_names = index_names + if index_names: + for index, block in enumerate(self.block_list): + block.rename(index) + self.qubits = circuit.nqubits + + def __call__(self): + return self.block_list + + def search_by_index(self, index: int): + """Find a block from its index, requires index_names == True""" + if not self._index_names: + raise BlockingError( + "You need to assign index names in order to use search_by_index." + ) + for block in self.block_list: + if block.name == index: + return block + raise BlockingError("No block found with index {}.".format(index)) + + def add_block(self, block: "Block"): + """Add a two qubits block.""" + if not set(block.qubits).issubset(range(self.qubits)): + raise BlockingError( + "The block can't be added to the circuit because it acts on different qubits" + ) + self.block_list.append(block) + + def circuit(self): + """Return the quantum circuit.""" + circuit = Circuit(self.qubits) + for block in self.block_list: + for gate in block.gates: + circuit.add(gate) + return circuit + + def remove_block(self, block: "Block"): + """Remove a block from the circuit blocks.""" + try: + self.block_list.remove(block) + except ValueError: + raise BlockingError( + "The block you are trying to remove is not present in the circuit blocks." + ) + + +def block_decomposition(circuit: Circuit, fuse: bool = True): + """Decompose a circuit into blocks of gates acting on two qubits. + + Args: + circuit (qibo.models.Circuit): circuit to be decomposed. + fuse (bool): fuse adjacent blocks acting on the same qubits. + + Return: + blocks (list): list of blocks that act on two qubits. + """ + if circuit.nqubits < 2: + raise BlockingError( + "Only circuits with at least two qubits can be decomposed with block_decomposition." + ) + initial_blocks = initial_block_decomposition(circuit) + if not fuse: + return initial_blocks + blocks = [] + while len(initial_blocks) > 0: + first_block = initial_blocks[0] + remove_list = [first_block] + if len(initial_blocks[1:]) > 0: + for second_block in initial_blocks[1:]: + try: + first_block = first_block.fuse(second_block) + remove_list.append(second_block) + except BlockingError: + if not first_block.commute(second_block): + break + blocks.append(first_block) + remove_gates(initial_blocks, remove_list) + return blocks + + +def initial_block_decomposition(circuit: Circuit): + """Decompose a circuit into blocks of gates acting on two qubits. + This decomposition is not minimal. + + Args: + circuit (qibo.models.Circuit): circuit to be decomposed. + + Return: + blocks (list): list of blocks that act on two qubits. + """ + blocks = [] + all_gates = list(circuit.queue) + two_qubit_gates = count_multi_qubit_gates(all_gates) + while two_qubit_gates > 0: + for idx, gate in enumerate(all_gates): + if len(gate.qubits) == 2: + qubits = gate.qubits + block_gates = _find_previous_gates(all_gates[0:idx], qubits) + block_gates.append(gate) + block_gates.extend(_find_successive_gates(all_gates[idx + 1 :], qubits)) + block = Block(qubits=qubits, gates=block_gates) + remove_gates(all_gates, block_gates) + two_qubit_gates -= 1 + blocks.append(block) + break + elif len(gate.qubits) > 2: + raise BlockingError( + "Gates targeting more than 2 qubits are not supported." + ) + # Now we need to deal with the remaining spare single qubit gates + while len(all_gates) > 0: + first_qubit = all_gates[0].qubits[0] + block_gates = gates_on_qubit(gatelist=all_gates, qubit=first_qubit) + remove_gates(all_gates, block_gates) + # Add other single qubits if there are still single qubit gates + if len(all_gates) > 0: + second_qubit = all_gates[0].qubits[0] + second_qubit_block_gates = gates_on_qubit( + gatelist=all_gates, qubit=second_qubit + ) + block_gates += second_qubit_block_gates + remove_gates(all_gates, second_qubit_block_gates) + block = Block(qubits=(first_qubit, second_qubit), gates=block_gates) + # In case there are no other spare single qubit gates create a block using a following qubit as placeholder + else: + block = Block( + qubits=(first_qubit, (first_qubit + 1) % circuit.nqubits), + gates=block_gates, + ) + blocks.append(block) + return blocks + + +def gates_on_qubit(gatelist, qubit): + """Return a list of all single qubit gates in gatelist acting on a specific qubit.""" + selected_gates = [] + for gate in gatelist: + if gate.qubits[0] == qubit: + selected_gates.append(gate) + return selected_gates + + +def remove_gates(gatelist, remove_list): + """Remove all gates present in remove_list from gatelist.""" + for gate in remove_list: + gatelist.remove(gate) + + +def count_2q_gates(gatelist: list): + """Return the number of two qubit gates in a list of gates.""" + return len([gate for gate in gatelist if len(gate.qubits) == 2]) + + +def count_multi_qubit_gates(gatelist: list): + """Return the number of multi qubit gates in a list of gates.""" + return len([gate for gate in gatelist if len(gate.qubits) >= 2]) + + +def _find_successive_gates(gates: list, qubits: tuple): + """Return a list containing all gates acting on qubits until a new two qubit gate acting on qubits is found.""" + successive_gates = [] + for qubit in qubits: + for gate in gates: + if (len(gate.qubits) == 1) and (gate.qubits[0] == qubit): + successive_gates.append(gate) + elif (len(gate.qubits) == 2) and (qubit in gate.qubits): + break + return successive_gates + + +def _find_previous_gates(gates: list, qubits: tuple): + """Return a list containing all gates acting on qubits.""" + previous_gates = [] + for gate in gates: + if gate.qubits[0] in qubits: + previous_gates.append(gate) + return previous_gates diff --git a/src/qibo/transpiler/optimizer.py b/src/qibo/transpiler/optimizer.py new file mode 100644 index 0000000000..d2525ff289 --- /dev/null +++ b/src/qibo/transpiler/optimizer.py @@ -0,0 +1,50 @@ +import networkx as nx + +from qibo import gates +from qibo.models import Circuit +from qibo.transpiler.abstract import Optimizer + + +class Preprocessing(Optimizer): + """Match the number of qubits of the circuit with the number of qubits of the chip if possible. + + Args: + connectivity (nx.Graph): hardware chip connectivity. + """ + + def __init__(self, connectivity: nx.Graph): + self.connectivity = connectivity + + def __call__(self, circuit: Circuit) -> Circuit: + physical_qubits = self.connectivity.number_of_nodes() + logical_qubits = circuit.nqubits + if logical_qubits > physical_qubits: + raise ValueError( + "The number of qubits in the circuit can't be greater than the number of physical qubits." + ) + if logical_qubits == physical_qubits: + return circuit + new_circuit = Circuit(physical_qubits) + for gate in circuit.queue: + new_circuit.add(gate) + return new_circuit + + +class Rearrange(Optimizer): + """Rearranges gates using qibo's fusion algorithm. + May reduce number of SWAPs when fixing for connectivity + but this has not been tested. + """ + + def __init__(self, max_qubits: int = 1): + self.max_qubits = max_qubits + + def __call__(self, circuit: Circuit): + fcircuit = circuit.fuse(max_qubits=self.max_qubits) + new = circuit.__class__(circuit.nqubits) + for fgate in fcircuit.queue: + if isinstance(fgate, gates.FusedGate): + new.add(gates.Unitary(fgate.matrix(), *fgate.qubits)) + else: + new.add(fgate) + return new diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py new file mode 100644 index 0000000000..a663ddbaef --- /dev/null +++ b/src/qibo/transpiler/pipeline.py @@ -0,0 +1,216 @@ +import networkx as nx +import numpy as np +from qibolab.native import NativeType + +from qibo.backends import NumpyBackend +from qibo.models import Circuit +from qibo.quantum_info.random_ensembles import random_statevector +from qibo.transpiler.abstract import Optimizer, Placer, Router, Unroller +from qibo.transpiler.optimizer import Preprocessing +from qibo.transpiler.placer import Trivial, assert_placement +from qibo.transpiler.router import ConnectivityError, assert_connectivity +from qibo.transpiler.star_connectivity import StarConnectivity +from qibo.transpiler.unroller import ( + DecompositionError, + NativeGates, + assert_decomposition, +) + + +class TranspilerPipelineError(Exception): + """Raise when an error occurs in the transpiler pipeline""" + + +def assert_circuit_equivalence( + original_circuit: Circuit, + transpiled_circuit: Circuit, + final_map: dict, + initial_map: dict = None, + test_states: list = None, + ntests: int = 3, +): + """Checks that the transpiled circuit agrees with the original using simulation. + + Args: + original_circuit (qibo.models.Circuit): Original circuit. + transpiled_circuit (qibo.models.Circuit): Transpiled circuit. + final_map (dict): logical-physical qubit mapping after routing. + initial_map (dict): logical_physical qubit mapping before routing, if None trivial initial map is used. + test_states (list): states on which the test is performed, if None 'ntests' random states will be tested. + ntests (int): number of random states tested. + """ + backend = NumpyBackend() + ordering = np.argsort(np.array(list(final_map.values()))) + if transpiled_circuit.nqubits != original_circuit.nqubits: + raise ValueError( + "Transpiled and original circuit do not have the same number of qubits." + ) + + if test_states is None: + test_states = [ + random_statevector(dims=2**original_circuit.nqubits, backend=backend) + for _ in range(ntests) + ] + if initial_map is not None: + reordered_test_states = [] + initial_map = np.array(list(initial_map.values())) + reordered_test_states = [ + transpose_qubits(initial_state, initial_map) + for initial_state in test_states + ] + else: + reordered_test_states = test_states + + for i in range(len(test_states)): + target_state = backend.execute_circuit( + original_circuit, initial_state=test_states[i] + ).state() + final_state = backend.execute_circuit( + transpiled_circuit, initial_state=reordered_test_states[i] + ).state() + final_state = transpose_qubits(final_state, ordering) + fidelity = np.abs(np.dot(np.conj(target_state), final_state)) + try: + np.testing.assert_allclose(fidelity, 1.0) + except AssertionError: + raise TranspilerPipelineError("Circuit equivalence not satisfied.") + + +def transpose_qubits(state: np.ndarray, qubits_ordering: np.ndarray): + """Reorders qubits of a given state vector. + + Args: + state (np.ndarray): final state of the circuit. + qubits_ordering (np.ndarray): final qubit ordering. + """ + original_shape = state.shape + state = np.reshape(state, len(qubits_ordering) * (2,)) + state = np.transpose(state, qubits_ordering) + return np.reshape(state, original_shape) + + +def assert_transpiling( + original_circuit: Circuit, + transpiled_circuit: Circuit, + connectivity: nx.Graph, + initial_layout: dict, + final_layout: dict, + native_gates: NativeType = NativeType.CZ, + 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 (nx.Graph): chip qubits connectivity. + initial_layout (dict): initial physical-logical qubit mapping. + final_layout (dict): final physical-logical qubit mapping. + native_gates: (NativeType): native gates supported by the hardware. + """ + assert_connectivity(circuit=transpiled_circuit, connectivity=connectivity) + assert_decomposition(circuit=transpiled_circuit, two_qubit_natives=native_gates) + if original_circuit.nqubits != transpiled_circuit.nqubits: + qubit_matcher = Preprocessing(connectivity=connectivity) + original_circuit = qubit_matcher(circuit=original_circuit) + assert_placement(circuit=original_circuit, layout=initial_layout) + assert_placement(circuit=transpiled_circuit, layout=final_layout) + if check_circuit_equivalence: + assert_circuit_equivalence( + original_circuit=original_circuit, + transpiled_circuit=transpiled_circuit, + initial_map=initial_layout, + final_map=final_layout, + ) + + +class Passes: + """Define a transpiler pipeline consisting of smaller transpiler steps that are applied sequentially: + + Args: + passes (list): list of passes to be applied sequentially, + if None default transpiler will be used, it requires hardware connectivity. + connectivity (nx.Graph): hardware qubit connectivity. + """ + + def __init__( + self, + passes: list = None, + connectivity: nx.Graph = None, + native_gates: NativeType = NativeType.CZ, + ): + self.native_gates = native_gates + if passes is None: + self.passes = self.default(connectivity) + else: + self.passes = passes + self.connectivity = connectivity + + def default(self, connectivity: nx.Graph): + """Return the default transpiler pipeline for the required hardware connectivity.""" + if not isinstance(connectivity, nx.Graph): + raise TranspilerPipelineError( + "Define the hardware chip connectivity to use default transpiler" + ) + default_passes = [] + # preprocessing + default_passes.append(Preprocessing(connectivity=connectivity)) + # default placer pass + default_passes.append(Trivial(connectivity=connectivity)) + # default_passes.append(ReverseTraversal(connectivity=connectivity, routing_algorithm=ShortestPaths(connectivity), depth=5)) + # default router pass + default_passes.append(StarConnectivity()) + # default_passes.append(ShortestPaths(connectivity=connectivity)) + # default unroller pass + default_passes.append(NativeGates(two_qubit_natives=self.native_gates)) + return default_passes + + def __call__(self, circuit): + self.initial_layout = None + final_layout = None + for transpiler_pass in self.passes: + if isinstance(transpiler_pass, Optimizer): + circuit = transpiler_pass(circuit) + elif isinstance(transpiler_pass, Placer): + if self.initial_layout == None: + self.initial_layout = transpiler_pass(circuit) + else: + raise TranspilerPipelineError( + "You are defining more than one placer pass." + ) + elif isinstance(transpiler_pass, Router): + if self.initial_layout is not None: + circuit, final_layout = transpiler_pass( + circuit, self.initial_layout + ) + else: + raise TranspilerPipelineError( + "Use a placement pass before routing." + ) + elif isinstance(transpiler_pass, Unroller): + circuit = transpiler_pass(circuit) + else: + raise TranspilerPipelineError( + "Unrecognised transpiler pass: ", transpiler_pass + ) + return circuit, final_layout + + def is_satisfied(self, circuit): + """Return True if the circuit respects the hardware connectivity and native gates, False otherwise. + + Args: + circuit (qibo.models.Circuit): circuit to be checked. + native_gates (NativeType): two qubit native gates. + """ + try: + assert_connectivity(circuit=circuit, connectivity=self.connectivity) + assert_decomposition(circuit=circuit, two_qubit_natives=self.native_gates) + return True + except ConnectivityError: + 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 new file mode 100644 index 0000000000..9f59c833c7 --- /dev/null +++ b/src/qibo/transpiler/placer.py @@ -0,0 +1,317 @@ +import random + +import networkx as nx + +from qibo import gates +from qibo.config import raise_error +from qibo.models import Circuit +from qibo.transpiler.abstract import Placer, Router, find_gates_qubits_pairs + + +class PlacementError(Exception): + """Raise for an error in the initial qubit placement""" + + +def assert_placement(circuit: Circuit, layout: dict) -> bool: + """Check if layout is correct and matches the number of qubits of the circuit. + + Args: + circuit (qibo.models.Circuit): Circuit model to check. + layout (dict): physical to logical qubit mapping. + + Raise PlacementError if the following conditions are not satisfied: + - layout is written in the correct form. + - layout matches the number of qubits in the circuit. + """ + assert_mapping_consistency(layout) + if circuit.nqubits > len(layout): + raise PlacementError( + "Layout can't be used on circuit. The circuit requires more qubits." + ) + if circuit.nqubits < len(layout): + raise PlacementError( + "Layout can't be used on circuit. Ancillary extra qubits need to be added to the circuit." + ) + + +def assert_mapping_consistency(layout): + """Check if layout is correct. + + Args: + layout (dict): physical to logical qubit mapping. + + Raise PlacementError if layout is not written in the correct form. + """ + values = sorted(layout.values()) + keys = list(layout) + ref_keys = ["q" + str(i) for i in range(len(keys))] + if keys != ref_keys: + raise PlacementError( + "Some physical qubits in the layout may be missing or duplicated." + ) + if values != list(range(len(values))): + raise PlacementError( + "Some logical qubits in the layout may be missing or duplicated." + ) + + +class Trivial(Placer): + """Place qubits according to the following simple notation: {'q0' : 0, 'q1' : 1, ..., 'qn' : n}. + + Attributes: + connectivity (networkx.Graph): chip connectivity. + """ + + def __init__(self, connectivity: nx.Graph = None): + self.connectivity = connectivity + + def __call__(self, circuit: Circuit): + """Find the trivial placement for the circuit. + + Args: + circuit (qibo.models.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 PlacementError( + "The number of nodes of the connectivity graph must match the number of qubits in the circuit" + ) + return dict( + zip( + list("q" + str(i) for i in range(circuit.nqubits)), + range(circuit.nqubits), + ) + ) + + +class Custom(Placer): + """Define a custom initial qubit mapping. + + Attributes: + map (list or dict): physical to logical qubit mapping, + example [1,2,0] or {"q0":1, "q1":2, "q2":0} to assign the + physical qubits 0;1;2 to the logical qubits 1;2;0 respectively. + connectivity (networkx.Graph): chip connectivity. + """ + + def __init__(self, map, connectivity=None): + self.connectivity = connectivity + self.map = map + + def __call__(self, circuit=None): + """Return the custom placement if it can be applied to the given circuit (if given). + + Args: + circuit (qibo.models.Circuit): circuit to be transpiled. + + Returns: + (dict): physical to logical qubit mapping. + """ + if isinstance(self.map, dict): + pass + elif isinstance(self.map, list): + self.map = dict( + zip(list("q" + str(i) for i in range(len(self.map))), self.map) + ) + else: + raise_error(TypeError, "Use dict or list to define mapping.") + if circuit is not None: + assert_placement(circuit, self.map) + else: + assert_mapping_consistency(self.map) + return self.map + + +class Subgraph(Placer): + """ + Subgraph isomorphism qubit placer, NP-complete it can take a long time + for large circuits. This initialization method may fail for very short circuits. + + Attributes: + connectivity (networkx.Graph): chip connectivity. + """ + + def __init__(self, connectivity): + self.connectivity = connectivity + + def __call__(self, circuit: Circuit): + """Find the initial layout of the given circuit using subgraph isomorphism. + + Args: + circuit (qibo.models.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: + raise_error( + ValueError, + "Circuit must contain at least two two qubit gates to implement subgraph placement.", + ) + circuit_subgraph = nx.Graph() + circuit_subgraph.add_nodes_from(range(self.connectivity.number_of_nodes())) + matcher = nx.algorithms.isomorphism.GraphMatcher( + self.connectivity, circuit_subgraph + ) + i = 0 + circuit_subgraph.add_edge(gates_qubits_pairs[i][0], gates_qubits_pairs[i][1]) + while matcher.subgraph_is_monomorphic() == True: + result = matcher + i += 1 + circuit_subgraph.add_edge( + gates_qubits_pairs[i][0], gates_qubits_pairs[i][1] + ) + matcher = nx.algorithms.isomorphism.GraphMatcher( + self.connectivity, circuit_subgraph + ) + if ( + self.connectivity.number_of_edges() + == circuit_subgraph.number_of_edges() + or i == len(gates_qubits_pairs) - 1 + ): + break + return {"q" + str(i): result.mapping[i] for i in range(len(result.mapping))} + + +class Random(Placer): + """ + Random initialization with greedy policy, let a maximum number of 2-qubit + gates can be applied without introducing any SWAP gate. + + Attributes: + connectivity (networkx.Graph): chip connectivity. + samples (int): number of initial random layouts tested. + """ + + def __init__(self, connectivity, samples=100): + self.connectivity = connectivity + self.samples = samples + + def __call__(self, circuit): + """Find an initial layout of the given circuit using random greedy algorithm. + + Args: + circuit (qibo.models.Circuit): Circuit to be transpiled. + + Returns: + (dict): physical to logical qubit mapping. + """ + gates_qubits_pairs = find_gates_qubits_pairs(circuit) + nodes = self.connectivity.number_of_nodes() + keys = list(self.connectivity.nodes()) + final_mapping = dict(zip(keys, range(nodes))) + final_graph = nx.relabel_nodes(self.connectivity, final_mapping) + final_mapping = { + "q" + str(i): final_mapping[i] for i in range(len(final_mapping)) + } + final_cost = self.cost(final_graph, gates_qubits_pairs) + for _ in range(self.samples): + mapping = dict(zip(keys, random.sample(range(nodes), nodes))) + graph = nx.relabel_nodes(self.connectivity, mapping) + cost = self.cost(graph, gates_qubits_pairs) + if cost == 0: + return {"q" + str(i): mapping[i] for i in range(len(mapping))} + if cost < final_cost: + final_graph = graph + final_mapping = {"q" + str(i): mapping[i] for i in range(len(mapping))} + final_cost = cost + return final_mapping + + @staticmethod + def cost(graph, gates_qubits_pairs): + """ + Compute the cost associated to an initial layout as the lengh of the reduced circuit. + + Args: + graph (networkx.Graph): current hardware qubit mapping. + gates_qubits_pairs (list): circuit representation. + + Returns: + (int): lengh of the reduced circuit. + """ + for allowed, gate in enumerate(gates_qubits_pairs): + if gate not in graph.edges(): + return len(gates_qubits_pairs) - allowed - 1 + return 0 + + +class ReverseTraversal(Placer): + """ + Place qubits based on the algorithm proposed in https://doi.org/10.48550/arXiv.1809.02573. + Works with ShortestPaths routing. + + Attributes: + connectivity (networkx.Graph): chip connectivity. + routing_algorithm (qibolab.transpilers.routing.Transpiler): routing algorithm. + depth (int): number of two qubit gates considered before finding initial layout. + if 'None' just one backward step will be implemented. + If depth is greater than the number of two qubit gates in the circuit, the circuit will be routed more than once. + Example: on a circuit with four two qubit gates A-B-C-D using depth = 6, + the routing will be performed on the circuit C-D-D-C-B-A. + """ + + def __init__(self, connectivity: nx.Graph, routing_algorithm: Router, depth=None): + self.connectivity = connectivity + self.routing_algorithm = routing_algorithm + self.depth = depth + + def __call__(self, circuit: Circuit): + """Find the initial layout of the given circuit using Reverse Traversal placement. + + Args: + circuit (qibo.models.Circuit): circuit to be transpiled. + + Returns: + (dict): physical to logical qubit mapping. + """ + + initial_placer = Trivial(self.connectivity) + initial_placement = initial_placer(circuit=circuit) + new_circuit = self.assemble_circuit(circuit) + final_placement = self.routing_step(initial_placement, new_circuit) + return final_placement + + def assemble_circuit(self, circuit: Circuit): + """Assemble a single circuit to apply Reverse Traversal placement based on depth. + Example: for a circuit with four two qubit gates A-B-C-D using depth = 6, + the function will return the circuit C-D-D-C-B-A. + + Args: + circuit (qibo.models.Circuit): circuit to be transpiled. + + Returns: + new_circuit (qibo.models.Circuit): assembled circuit to perform Reverse Traversal placement. + """ + + if self.depth is None: + return circuit.invert() + gates_qubits_pairs = find_gates_qubits_pairs(circuit) + circuit_gates = len(gates_qubits_pairs) + if circuit_gates == 0: + raise ValueError("The circuit must contain at least a two qubit gate.") + repetitions, remainder = divmod(self.depth, circuit_gates) + assembled_gates_qubits_pairs = [] + for _ in range(repetitions): + assembled_gates_qubits_pairs += gates_qubits_pairs[:] + gates_qubits_pairs.reverse() + assembled_gates_qubits_pairs += gates_qubits_pairs[0:remainder] + new_circuit = Circuit(circuit.nqubits) + 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): + """Perform routing of the circuit. + + Args: + layout (dict): intial qubit layout. + circuit (qibo.models.Circuit): circuit to be routed. + """ + + _, final_mapping = self.routing_algorithm(circuit, layout) + return final_mapping diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py new file mode 100644 index 0000000000..3abae571c0 --- /dev/null +++ b/src/qibo/transpiler/router.py @@ -0,0 +1,658 @@ +from copy import deepcopy + +import matplotlib.pyplot as plt +import networkx as nx +import numpy as np +from more_itertools import pairwise + +from qibo import gates +from qibo.config import log, raise_error +from qibo.models import Circuit +from qibo.transpiler.abstract import Router, find_gates_qubits_pairs +from qibo.transpiler.blocks import Block, CircuitBlocks +from qibo.transpiler.placer import assert_placement + + +class ConnectivityError(Exception): + """Raise for an error in the connectivity""" + + +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 (qibo.models.Circuit): circuit model to check. + connectivity (networkx.graph): chip connectivity. + """ + + for gate in circuit.queue: + if len(gate.qubits) > 2 and not isinstance(gate, gates.M): + raise ConnectivityError(f"{gate.name} acts on more than two qubits.") + if len(gate.qubits) == 2: + if (gate.qubits[0], gate.qubits[1]) not in connectivity.edges: + raise ConnectivityError( + "Circuit does not respect connectivity. " + f"{gate.name} acts on {gate.qubits}." + ) + + +# TODO: make this class work with CircuitMap +class ShortestPaths(Router): + """A class to perform initial qubit mapping and connectivity matching. + + Properties: + sampling_split (float): fraction of paths tested (between 0 and 1). + + Attributes: + connectivity (networkx.Graph): chip connectivity. + verbose (bool): print info messages. + initial_layout (dict): initial physical to logical qubit mapping + added_swaps (int): number of swaps added to the circuit to match connectivity. + _gates_qubits_pairs (list): quantum circuit represented as a list (only 2 qubit gates). + _mapping (dict): circuit to physical qubit mapping during transpiling. + _graph (networkx.graph): qubit mapped as nodes of the connectivity graph. + _qubit_map (np.array): circuit to physical qubit mapping during transpiling as vector. + _circuit_position (int): position in the circuit. + + """ + + def __init__(self, connectivity: nx.Graph, sampling_split=1.0, verbose=False): + self.connectivity = connectivity + self.sampling_split = sampling_split + self.verbose = verbose + self.initial_layout = None + self._added_swaps = 0 + self.final_map = None + self._gates_qubits_pairs = None + self._mapping = None + self._swap_map = None + self._added_swaps_list = [] + self._graph = None + self._qubit_map = None + self._transpiled_circuit = None + self._circuit_position = 0 + + def __call__(self, circuit: Circuit, initial_layout): + """Circuit connectivity matching. + + Args: + circuit (qibo.models.Circuit): circuit to be matched to hardware connectivity. + initial_layout (dict): initial qubit mapping. + + Returns: + hardware_mapped_circuit (qibo.models.Circuit): circut mapped to hardware topology. + final_mapping (dict): final qubit mapping. + """ + self._mapping = initial_layout + init_qubit_map = np.asarray(list(initial_layout.values())) + self.initial_checks(circuit.nqubits) + self._gates_qubits_pairs = find_gates_qubits_pairs(circuit) + self._mapping = dict(zip(range(len(initial_layout)), initial_layout.values())) + self._graph = nx.relabel_nodes(self.connectivity, self._mapping) + self._qubit_map = np.sort(init_qubit_map) + self._swap_map = deepcopy(init_qubit_map) + self.first_transpiler_step(circuit) + while len(self._gates_qubits_pairs) != 0: + self.transpiler_step(circuit) + hardware_mapped_circuit = self.remap_circuit( + np.argsort(init_qubit_map), original_circuit=circuit + ) + final_mapping = { + "q" + str(j): self._swap_map[j] + for j in range(self._graph.number_of_nodes()) + } + return hardware_mapped_circuit, final_mapping + + def transpiler_step(self, qibo_circuit): + """Transpilation step. Find new mapping, add swap gates and apply gates that can be run with this configuration. + + Args: + qibo_circuit (qibo.models.Circuit): circuit to be transpiled. + """ + len_before_step = len(self._gates_qubits_pairs) + path, meeting_point = self.relocate() + self.add_swaps(path, meeting_point) + self.update_qubit_map() + self.add_gates(qibo_circuit, len_before_step - len(self._gates_qubits_pairs)) + + def first_transpiler_step(self, qibo_circuit): + """First transpilation step. Apply gates that can be run with the initial qubit mapping. + + Args: + qibo_circuit (qibo.models.Circuit): circuit to be transpiled. + """ + self._circuit_position = 0 + self._added_swaps = 0 + self._added_swaps_list = [] + len_2q_circuit = len(self._gates_qubits_pairs) + self._gates_qubits_pairs = self.reduce(self._graph) + self.add_gates(qibo_circuit, len_2q_circuit - len(self._gates_qubits_pairs)) + + @property + def sampling_split(self): + return self._sampling_split + + @sampling_split.setter + def sampling_split(self, sampling_split): + """Set the sampling split. + + Args: + sampling_split (float): define fraction of shortest path tested. + """ + + if 0.0 < sampling_split <= 1.0: + self._sampling_split = sampling_split + else: + raise_error(ValueError, "Sampling_split must be in (0:1].") + + def reduce(self, graph): + """Reduce the circuit, delete a 2-qubit gate if it can be applied on the current configuration. + + Args: + graph (networkx.Graph): current hardware qubit mapping. + + Returns: + new_circuit (list): reduced circuit. + """ + new_circuit = self._gates_qubits_pairs.copy() + while ( + new_circuit != [] + and (new_circuit[0][0], new_circuit[0][1]) in graph.edges() + ): + del new_circuit[0] + return new_circuit + + def map_list(self, path): + """Return all possible walks of qubits, or a fraction, for a given path. + + Args: + path (list): path to move qubits. + + Returns: + mapping_list (list): all possible walks of qubits, or a fraction of them based on self.sampling_split, for a given path. + meeting_point_list (list): qubit meeting point for each path. + """ + path_ends = [path[0], path[-1]] + path_middle = path[1:-1] + mapping_list = [] + meeting_point_list = [] + test_paths = range(len(path) - 1) + if self.sampling_split != 1.0: + test_paths = np.random.choice( + test_paths, + size=int(np.ceil(len(test_paths) * self.sampling_split)), + replace=False, + ) + for i in test_paths: + values = path_middle[:i] + path_ends + path_middle[i:] + mapping = dict(zip(path, values)) + mapping_list.append(mapping) + meeting_point_list.append(i) + return mapping_list, meeting_point_list + + def relocate(self): + """A small greedy algorithm to decide which path to take, and how qubits should walk. + + Returns: + final_path (list): best path to move qubits. + meeting_point (int): qubit meeting point in the path. + """ + nodes = self._graph.number_of_nodes() + circuit = self.reduce(self._graph) + final_circuit = circuit + keys = list(range(nodes)) + final_graph = self._graph + final_mapping = dict(zip(keys, keys)) + # Consider all shortest paths + path_list = [ + p + for p in nx.all_shortest_paths( + self._graph, source=circuit[0][0], target=circuit[0][1] + ) + ] + self._added_swaps += len(path_list[0]) - 2 + # Here test all paths + for path in path_list: + # map_list uses self.sampling_split + list_, meeting_point_list = self.map_list(path) + for j, mapping in enumerate(list_): + new_graph = nx.relabel_nodes(self._graph, mapping) + new_circuit = self.reduce(new_graph) + # Greedy looking for the optimal path and the optimal walk on this path + if len(new_circuit) < len(final_circuit): + final_graph = new_graph + final_circuit = new_circuit + final_mapping = mapping + final_path = path + meeting_point = meeting_point_list[j] + self._graph = final_graph + self._mapping = final_mapping + self._gates_qubits_pairs = final_circuit + return final_path, meeting_point + + def initial_checks(self, qubits): + """Initialize the transpiled circuit and check if it can be mapped to the defined connectivity. + + Args: + qubits (int): number of qubits in the circuit to be transpiled. + """ + nodes = self.connectivity.number_of_nodes() + if qubits > nodes: + raise_error( + ValueError, + "There are not enough physical qubits in the hardware to map the circuit.", + ) + if qubits == nodes: + new_circuit = Circuit(nodes) + else: + if self.verbose: + log.info( + "You are using more physical qubits than required by the circuit, some ancillary qubits will be added to the circuit." + ) + new_circuit = Circuit(nodes) + assert_placement(new_circuit, self._mapping) + self._transpiled_circuit = new_circuit + + def add_gates(self, qibo_circuit: Circuit, matched_gates): + """Add one and two qubit gates to transpiled circuit until connectivity is matched. + + Args: + qibo_circuit (qibo.models.Circuit): circuit to be transpiled. + matched_gates (int): number of two qubit gates that can be applied with the current qubit mapping. + """ + index = 0 + while self._circuit_position < len(qibo_circuit.queue): + gate = qibo_circuit.queue[self._circuit_position] + if isinstance(gate, gates.M): + measured_qubits = gate.qubits + self._transpiled_circuit.add( + gate.on_qubits( + { + measured_qubits[i]: self._qubit_map[measured_qubits[i]] + for i in range(len(measured_qubits)) + } + ) + ) + self._circuit_position += 1 + elif len(gate.qubits) == 1: + self._transpiled_circuit.add( + gate.on_qubits({gate.qubits[0]: self._qubit_map[gate.qubits[0]]}) + ) + self._circuit_position += 1 + else: + index += 1 + if index == matched_gates + 1: + break + self._transpiled_circuit.add( + gate.on_qubits( + { + gate.qubits[0]: self._qubit_map[gate.qubits[0]], + gate.qubits[1]: self._qubit_map[gate.qubits[1]], + } + ) + ) + self._circuit_position += 1 + + def add_swaps(self, path, meeting_point): + """Add swaps to the transpiled circuit to move qubits. + + Args: + path (list): path to move qubits. + meeting_point (int): qubit meeting point in the path. + """ + forward = path[0 : meeting_point + 1] + backward = list(reversed(path[meeting_point + 1 :])) + if len(forward) > 1: + for f1, f2 in pairwise(forward): + gate = gates.SWAP(self._qubit_map[f1], self._qubit_map[f2]) + self._transpiled_circuit.add(gate) + self._added_swaps_list.append(gate) + + if len(backward) > 1: + for b1, b2 in pairwise(backward): + gate = gates.SWAP(self._qubit_map[b1], self._qubit_map[b2]) + self._transpiled_circuit.add(gate) + self._added_swaps_list.append(gate) + + def update_swap_map(self, swap: tuple): + """Update the qubit swap map.""" + temp = self._swap_map[swap[0]] + self._swap_map[swap[0]] = self._swap_map[swap[1]] + self._swap_map[swap[1]] = temp + + def update_qubit_map(self): + """Update the qubit mapping after adding swaps.""" + old_mapping = self._qubit_map.copy() + for key, value in self._mapping.items(): + self._qubit_map[value] = old_mapping[key] + + @property + def added_swaps(self): + """Number of added swaps during transpiling.""" + return self._added_swaps + + def remap_circuit(self, qubit_map, original_circuit: Circuit): + """Map logical to physical qubits in a circuit. + + Args: + circuit (qibo.models.Circuit): qibo circuit to be remapped. + qubit_map (np.array): new qubit mapping. + + Returns: + new_circuit (qibo.models.Circuit): transpiled circuit mapped with initial qubit mapping. + """ + new_circuit = Circuit(self._transpiled_circuit.nqubits) + for gate in self._transpiled_circuit.queue: + new_circuit.add(gate.on_qubits({q: qubit_map[q] for q in gate.qubits})) + if gate in self._added_swaps_list: + self.update_swap_map(tuple(qubit_map[gate.qubits[i]] for i in range(2))) + return new_circuit + + +class CircuitMap: + """Class to keep track of the circuit and physical-logical mapping during routing, + this class also implements the initial two qubit blocks decomposition. + + Args: + initial_layout (dict): initial logical-physical qubit mapping. + circuit (Circuit): circuit to be routed. + + Attributes: + circuit_blocks (CircuitBlocks): list of two qubit blocks of the circuit. + _physical_logical (list): current logical to physical qubit mapping. + _circuit_logical (list): initial circuit to current logical circuit mapping. + _routed_blocks (CircuitBlocks): current routed circuit blocks. + _swaps (int): number of added swaps. + """ + + def __init__(self, initial_layout: dict, circuit: Circuit): + self.circuit_blocks = CircuitBlocks(circuit, index_names=True) + self._circuit_logical = list(range(len(initial_layout))) + self._physical_logical = list(initial_layout.values()) + self._routed_blocks = CircuitBlocks(Circuit(circuit.nqubits)) + self._swaps = 0 + + def blocks_qubits_pairs(self): + """Return a list containing the qubit pairs of each block.""" + return [block.qubits for block in self.circuit_blocks()] + + def execute_block(self, block: Block): + """Execute a block by removing it from the circuit representation + and adding it to the routed circuit. + """ + self._routed_blocks.add_block(block.on_qubits(self.get_physical_qubits(block))) + self.circuit_blocks.remove_block(block) + + def routed_circuit(self): + """Return qibo circuit of the routed circuit.""" + return self._routed_blocks.circuit() + + def final_layout(self): + """Return the final physical-circuit qubits mapping.""" + unsorted_dict = { + "q" + str(self.circuit_to_physical(i)): i + for i in range(len(self._circuit_logical)) + } + return dict(sorted(unsorted_dict.items())) + + def update(self, swap: tuple): + """Update the logical-physical qubit mapping after applying a SWAP + and add the SWAP gate to the routed blocks, the swap is represented by a tuple containing + the logical qubits to be swapped. + """ + physical_swap = self.logical_to_physical(swap) + self._routed_blocks.add_block( + Block(qubits=physical_swap, gates=[gates.SWAP(*physical_swap)]) + ) + self._swaps += 1 + idx_0, idx_1 = self._circuit_logical.index( + swap[0] + ), self._circuit_logical.index(swap[1]) + self._circuit_logical[idx_0], self._circuit_logical[idx_1] = swap[1], swap[0] + + def get_logical_qubits(self, block: Block): + """Return the current logical qubits where a block is acting""" + return self.circuit_to_logical(block.qubits) + + def get_physical_qubits(self, block: Block or int): + """Return the physical qubits where a block is acting.""" + if isinstance(block, int): + block = self.circuit_blocks.search_by_index(block) + return self.logical_to_physical(self.get_logical_qubits(block)) + + def logical_to_physical(self, logical_qubits: tuple): + """Return the physical qubits associated to the logical qubits.""" + return tuple(self._physical_logical.index(logical_qubits[i]) for i in range(2)) + + def circuit_to_logical(self, circuit_qubits: tuple): + """Return the current logical qubits associated to the initial circuit qubits.""" + return tuple(self._circuit_logical[circuit_qubits[i]] for i in range(2)) + + def circuit_to_physical(self, circuit_qubit: int): + """Return the current physical qubit associated to an initial circuit qubit.""" + return self._physical_logical.index(self._circuit_logical[circuit_qubit]) + + +MAX_ITER = 10000 + + +class Sabre(Router): + def __init__(self, connectivity: nx.Graph, lookahead: int = 2, decay: float = 0.6): + """Routing algorithm proposed in + https://doi.org/10.48550/arXiv.1809.02573 + + Args: + connectivity (dict): hardware chip connectivity. + lookahead (int): lookahead factor, how many dag layers will be considered in computing the cost. + decay (float): value in interval [0,1]. + How the weight of the distance in the dag layers decays in computing the cost. + """ + self.connectivity = connectivity + self.lookahead = lookahead + self.decay = decay + self._dist_matrix = None + self._dag = None + self._front_layer = None + self.circuit = None + self._memory_map = None + + def __call__(self, circuit, initial_layout): + """Route the circuit. + + Args: + circuit (qibo.models.Circuit): circuit to be routed. + initial_layout (dict): initial physical to logical qubit mapping. + + Returns: + (qibo.models.Circuit): routed circuit. + """ + self.preprocessing(circuit=circuit, initial_layout=initial_layout) + while self._dag.number_of_nodes() != 0: + execute_block_list = self.check_execution() + if execute_block_list is not None: + self.execute_blocks(execute_block_list) + else: + self.find_new_mapping() + return self.circuit.routed_circuit(), self.circuit.final_layout() + + def preprocessing(self, circuit: Circuit, initial_layout): + """The following objects will be initialised: + - circuit: class to represent circuit and to perform logical-physical qubit mapping. + - _dist_matrix: matrix reporting the shortest path lengh between all node pairs. + - _dag: direct acyclic graph of the circuit based on commutativity. + - _memory_map: list to remember previous SWAP moves. + - _front_layer: list containing the blocks to be executed. + """ + self.circuit = CircuitMap(initial_layout, circuit) + self._dist_matrix = nx.floyd_warshall_numpy(self.connectivity) + self._dag = create_dag(self.circuit.blocks_qubits_pairs()) + self._memory_map = [] + self.update_dag_layers() + self.update_front_layer() + + def update_dag_layers(self): + for layer, nodes in enumerate(nx.topological_generations(self._dag)): + for node in nodes: + self._dag.nodes[node]["layer"] = layer + + def update_front_layer(self): + """Update the front layer of the dag.""" + self._front_layer = self.get_dag_layer(0) + + def get_dag_layer(self, n_layer): + """Return the n topological layer of the dag.""" + return [node[0] for node in self._dag.nodes(data="layer") if node[1] == n_layer] + + @property + def added_swaps(self): + """Number of SWAP gates added to the circuit during routing""" + return self.circuit._swaps + + def find_new_mapping(self): + """Find the new best mapping by adding one swap.""" + candidates_evaluation = {} + self._memory_map.append(deepcopy(self.circuit._circuit_logical)) + for candidate in self.swap_candidates(): + candidates_evaluation[candidate] = self.compute_cost(candidate) + best_candidate = min(candidates_evaluation, key=candidates_evaluation.get) + self.circuit.update(best_candidate) + + def compute_cost(self, candidate): + """Compute the cost associated to a possible SWAP candidate.""" + temporary_circuit = deepcopy(self.circuit) + temporary_circuit.update(candidate) + if not self.check_new_mapping(temporary_circuit._circuit_logical): + return float("inf") + tot_distance = 0.0 + weight = 1.0 + for layer in range(self.lookahead + 1): + layer_gates = self.get_dag_layer(layer) + avg_layer_distance = 0.0 + for gate in layer_gates: + qubits = temporary_circuit.get_physical_qubits(gate) + avg_layer_distance += ( + self._dist_matrix[qubits[0], qubits[1]] - 1.0 + ) / len(layer_gates) + tot_distance += weight * avg_layer_distance + weight *= self.decay + return tot_distance + + def check_new_mapping(self, map): + """Check that the candidate will generate a new qubit mapping in order to avoid ending up in infinite cycles. + If the mapping is not new the cost associated to that candidate will be infinite. + """ + if map in self._memory_map: + return False + return True + + def swap_candidates(self): + """Return a list of possible candidate SWAPs (to be applied on logical qubits directly). + The possible candidates are the ones sharing at least one qubit with a block in the front layer. + """ + candidates = [] + for block in self._front_layer: + qubits = self.circuit.get_physical_qubits(block) + for qubit in qubits: + for connected in self.connectivity.neighbors(qubit): + candidate = tuple( + sorted( + ( + self.circuit._physical_logical[qubit], + self.circuit._physical_logical[connected], + ) + ) + ) + if candidate not in candidates: + candidates.append(candidate) + return candidates + + def check_execution(self): + """Check if some gatesblocks in the front layer can be executed in the current configuration. + + Returns: + list of executable blocks if there are, None otherwise. + """ + executable_blocks = [] + for block in self._front_layer: + qubits = self.circuit.get_physical_qubits(block) + if ( + qubits in self.connectivity.edges + or not self.circuit.circuit_blocks.search_by_index(block).entangled + ): + executable_blocks.append(block) + if len(executable_blocks) == 0: + return None + return executable_blocks + + def execute_blocks(self, blocklist: list): + """Execute a list of blocks: + -Remove the correspondent nodes from the dag and circuit representation. + -Add the executed blocks to the routed circuit. + -Update the dag layers and front layer. + -Reset the mapping memory. + """ + for block_id in blocklist: + block = self.circuit.circuit_blocks.search_by_index(block_id) + self.circuit.execute_block(block) + self._dag.remove_node(block_id) + self.update_dag_layers() + self.update_front_layer() + self._memory_map = [] + + +def draw_dag(dag: nx.DiGraph, filename=None): # pragma: no cover + """Draw a direct acyclic graph in topological order. + + Args: + dag (nx.DiGraph): dag to be shown + filename (str): name of the saved image, if None the image will be showed. + """ + for layer, nodes in enumerate(nx.topological_generations(dag)): + for node in nodes: + dag.nodes[node]["layer"] = layer + pos = nx.multipartite_layout(dag, subset_key="layer") + fig, ax = plt.subplots() + nx.draw_networkx(dag, pos=pos, ax=ax) + ax.set_title("DAG layout in topological order") + fig.tight_layout() + if filename is None: + plt.show() + else: + plt.savefig(filename) + + +def create_dag(gates_qubits_pairs): + """Create direct acyclic graph (dag) of the circuit based on two qubit gates commutativity relations. + + Args: + gates_qubits_pairs (list): list of qubits tuples where gates/blocks acts. + + Returns: + (nx.DiGraph): dag of the circuit. + """ + dag = nx.DiGraph() + dag.add_nodes_from(range(len(gates_qubits_pairs))) + # Find all successors + connectivity_list = [] + for idx, gate in enumerate(gates_qubits_pairs): + 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): + saturated_qubits.append(qubit) + connectivity_list.append((idx, next_idx + idx + 1)) + if len(saturated_qubits) >= 2: + break + dag.add_edges_from(connectivity_list) + return remove_redundant_connections(dag) + + +def remove_redundant_connections(dag: nx.Graph): + """Remove redundant connection from a DAG unsing transitive reduction.""" + new_dag = nx.DiGraph() + new_dag.add_nodes_from(range(dag.number_of_nodes())) + transitive_reduction = nx.transitive_reduction(dag) + new_dag.add_edges_from(transitive_reduction.edges) + return new_dag diff --git a/src/qibo/transpiler/star_connectivity.py b/src/qibo/transpiler/star_connectivity.py new file mode 100644 index 0000000000..110b243178 --- /dev/null +++ b/src/qibo/transpiler/star_connectivity.py @@ -0,0 +1,164 @@ +from qibo import gates +from qibo.config import log, raise_error +from qibo.transpiler.abstract import Router + + +def find_connected_qubit(qubits, queue, hardware_qubits): + """Helper method for :meth:`qibolab.transpilers.fix_connecivity`. + + Finds which qubit should be mapped to hardware middle qubit + by looking at the two-qubit gates that follow. + """ + possible_qubits = set(qubits) + for next_gate in queue: + if len(next_gate.qubits) == 2: + possible_qubits &= {hardware_qubits.index(q) for q in next_gate.qubits} + if not possible_qubits: + # freedom of choice + return qubits[0] + elif len(possible_qubits) == 1: + return possible_qubits.pop() + # freedom of choice + return qubits[0] + + +class StarConnectivity(Router): + """Transforms an arbitrary circuit to one that can be executed on hardware. + This transpiler produces a circuit that respects the following connectivity: + + q + | + q -- q -- q + | + q + + by adding SWAP gates when needed. + + Args: + connectivity (networkx.Graph): chip connectivity, not used for this transpiler. + middle_qubit (int): qubit id of the qubit that is in the middle of the star. + verbose (bool): print info messages. + """ + + def __init__(self, connectivity=None, middle_qubit=2, verbose=False): + self.middle_qubit = middle_qubit + self.verbose = verbose + + def tlog(self, message): + """Print messages only if ``verbose`` was set to ``True``.""" + if self.verbose: + log.info(message) + + def is_satisfied(self, circuit): + """Checks if a circuit respects connectivity constraints. + + Args: + circuit (qibo.models.Circuit): Circuit model to check. + middle_qubit (int): Hardware middle qubit. + verbose (bool): If ``True`` it prints debugging log messages. + + Returns ``True`` if the following conditions are satisfied: + - Circuit does not contain more than two-qubit gates. + - All two-qubit gates have qubit 0 as target or control. + + otherwise returns ``False``. + """ + for gate in circuit.queue: + if len(gate.qubits) > 2 and not isinstance(gate, gates.M): + self.tlog(f"{gate.name} acts on more than two qubits.") + return False + elif len(gate.qubits) == 2: + if self.middle_qubit not in gate.qubits: + self.tlog( + "Circuit does not respect connectivity. " + f"{gate.name} acts on {gate.qubits}." + ) + return False + + self.tlog("Circuit respects connectivity.") + return True + + def __call__(self, circuit, initial_layout=None): + """Apply the transpiler transformation on a given circuit. + + Args: + circuit (qibo.models.Circuit): The original Qibo circuit to transform. + This circuit must contain up to two-qubit gates. + + Returns: + new (qibo.models.Circuit): Qibo circuit that performs the same operation + as the original but respects the hardware connectivity. + hardware_qubits (list): List that maps logical to hardware qubits. + This is required for transforming final measurements. + """ + + middle_qubit = self.middle_qubit + # find the number of qubits for hardware circuit + if circuit.nqubits == 1: + nqubits = 1 + else: + nqubits = max(circuit.nqubits, middle_qubit + 1) + # new circuit object that will be compatible with hardware connectivity + new = circuit.__class__(nqubits) + # list to maps logical to hardware qubits + hardware_qubits = list(range(nqubits)) + + # find initial qubit mapping + for i, gate in enumerate(circuit.queue): + if len(gate.qubits) == 2: + if middle_qubit not in gate.qubits: + new_middle = find_connected_qubit( + gate.qubits, circuit.queue[i + 1 :], hardware_qubits + ) + hardware_qubits[middle_qubit], hardware_qubits[new_middle] = ( + hardware_qubits[new_middle], + hardware_qubits[middle_qubit], + ) + break + + # the first SWAP is not needed as it can be applied via virtual mapping + add_swap = False + for i, gate in enumerate(circuit.queue): + # map gate qubits to hardware + qubits = tuple(hardware_qubits.index(q) for q in gate.qubits) + if isinstance(gate, gates.M): + new_gate = gates.M(*qubits, **gate.init_kwargs) + new_gate.result = gate.result + new.add(new_gate) + continue + + if len(qubits) > 2: + raise_error( + NotImplementedError, + "Transpiler does not support gates targeting more than two-qubits.", + ) + + elif len(qubits) == 2 and middle_qubit not in qubits: + # find which qubit should be moved to 0 + new_middle = find_connected_qubit( + qubits, circuit.queue[i + 1 :], hardware_qubits + ) + # update hardware qubits according to the swap + hardware_qubits[middle_qubit], hardware_qubits[new_middle] = ( + hardware_qubits[new_middle], + hardware_qubits[middle_qubit], + ) + if add_swap: + 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) + + # add gate to the hardware circuit + if isinstance(gate, gates.Unitary): + # gates.Unitary requires matrix as first argument + from qibo.backends import NumpyBackend + + backend = NumpyBackend() + matrix = gate.matrix(backend) + new.add(gate.__class__(matrix, *qubits, **gate.init_kwargs)) + else: + new.add(gate.__class__(*qubits, **gate.init_kwargs)) + if len(qubits) == 2: + add_swap = True + hardware_qubits_keys = ["q" + str(i) for i in range(5)] + return new, dict(zip(hardware_qubits_keys, hardware_qubits)) diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py new file mode 100644 index 0000000000..c4171debf0 --- /dev/null +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -0,0 +1,253 @@ +import numpy as np +from scipy.linalg import expm + +from qibo import gates, matrices +from qibo.config import raise_error + +magic_basis = np.array( + [[1, -1j, 0, 0], [0, 0, 1, -1j], [0, 0, -1, -1j], [1, 1j, 0, 0]] +) / np.sqrt(2) + +bell_basis = np.array( + [[1, 1, 0, 0], [0, 0, 1, 1], [0, 0, 1, -1], [1, -1, 0, 0]] +) / np.sqrt(2) + +H = np.array([[1, 1], [1, -1]]) / np.sqrt(2) + + +def u3_decomposition(unitary): + """Decomposes arbitrary one-qubit gates to U3. + + Args: + unitary (np.ndarray): Unitary 2x2 matrix to be decomposed. + + Returns: + theta, phi, lam: parameters of U3 gate. + """ + # https://github.com/Qiskit/qiskit-terra/blob/d2e3340adb79719f9154b665e8f6d8dc26b3e0aa/qiskit/quantum_info/synthesis/one_qubit_decompose.py#L221 + su2 = unitary / np.sqrt(np.linalg.det(unitary)) + theta = 2 * np.arctan2(abs(su2[1, 0]), abs(su2[0, 0])) + plus = np.angle(su2[1, 1]) + minus = np.angle(su2[1, 0]) + phi = plus + minus + lam = plus - minus + return theta, phi, lam + + +def calculate_psi(unitary): + """Solves the eigenvalue problem of UT_U. + + See step (1) of Appendix A in arXiv:quant-ph/0011050. + + Args: + unitary (np.ndarray): Unitary matrix of the gate we are + decomposing in the computational basis. + + Returns: + Eigenvectors (in the computational basis) and eigenvalues + of UT_U. + """ + # write unitary in magic basis + u_magic = np.dot(np.dot(np.conj(magic_basis.T), unitary), magic_basis) + # construct and diagonalize UT_U + ut_u = np.dot(u_magic.T, u_magic) + # When the matrix given to np.linalg.eig is a diagonal matrix up to machine precision the decomposition + # is not accurate anymore. decimals = 20 works for random 2q Clifford unitaries. + eigvals, psi_magic = np.linalg.eig(np.round(ut_u, decimals=20)) + # orthogonalize eigenvectors in the case of degeneracy (Gram-Schmidt) + psi_magic, _ = np.linalg.qr(psi_magic) + # write psi in computational basis + psi = np.dot(magic_basis, psi_magic) + return psi, eigvals + + +def schmidt_decompose(state): + """Decomposes a two-qubit product state to its single-qubit parts.""" + u, d, v = np.linalg.svd(np.reshape(state, (2, 2))) + if not np.allclose(d, [1, 0]): # pragma: no cover + raise_error( + ValueError, + f"Unexpected singular values: {d}\nCan only decompose product states.", + ) + return u[:, 0], v[0] + + +def calculate_single_qubit_unitaries(psi): + """Calculates local unitaries that maps a maximally entangled basis to the magic basis. + + See Lemma 1 of Appendix A in arXiv:quant-ph/0011050. + + Args: + psi (np.ndarray): Maximally entangled two-qubit states that define a basis. + + Returns: + Local unitaries UA and UB that map the given basis to the magic basis. + """ + + # TODO: Handle the case where psi is not real in the magic basis + psi_magic = np.dot(np.conj(magic_basis).T, psi) + if not np.allclose(psi_magic.imag, np.zeros_like(psi_magic)): # pragma: no cover + raise_error(NotImplementedError, "Given state is not real in the magic basis.") + psi_bar = np.copy(psi).T + + # find e and f by inverting (A3), (A4) + ef = (psi_bar[0] + 1j * psi_bar[1]) / np.sqrt(2) + e_f_ = (psi_bar[0] - 1j * psi_bar[1]) / np.sqrt(2) + e, f = schmidt_decompose(ef) + e_, f_ = schmidt_decompose(e_f_) + # find exp(1j * delta) using (A5a) + ef_ = np.kron(e, f_) + phase = 1j * np.sqrt(2) * np.dot(np.conj(ef_), psi_bar[2]) + + # construct unitaries UA, UB using (A6a), (A6b) + ua = np.tensordot([1, 0], np.conj(e), axes=0) + phase * np.tensordot( + [0, 1], np.conj(e_), axes=0 + ) + ub = np.tensordot([1, 0], np.conj(f), axes=0) + np.conj(phase) * np.tensordot( + [0, 1], np.conj(f_), axes=0 + ) + return ua, ub + + +def calculate_diagonal(unitary, ua, ub, va, vb): + """Calculates Ud matrix that can be written as exp(-iH). + + See Eq. (A1) in arXiv:quant-ph/0011050. + Ud is diagonal in the magic and Bell basis. + """ + # normalize U_A, U_B, V_A, V_B so that detU_d = 1 + # this is required so that sum(lambdas) = 0 + # and Ud can be written as exp(-iH) + det = np.linalg.det(unitary) ** (1 / 16) + ua *= det + ub *= det + va *= det + vb *= det + u_dagger = np.conj(np.kron(ua, ub).T) + v_dagger = np.conj(np.kron(va, vb).T) + ud = np.dot(np.dot(u_dagger, unitary), v_dagger) + return ua, ub, ud, va, vb + + +def magic_decomposition(unitary): + """Decomposes an arbitrary unitary to (A1) from arXiv:quant-ph/0011050.""" + psi, eigvals = calculate_psi(unitary) + psi_tilde = np.conj(np.sqrt(eigvals)) * np.dot(unitary, psi) + va, vb = calculate_single_qubit_unitaries(psi) + ua_dagger, ub_dagger = calculate_single_qubit_unitaries(psi_tilde) + ua, ub = np.conj(ua_dagger.T), np.conj(ub_dagger.T) + return calculate_diagonal(unitary, ua, ub, va, vb) + + +def to_bell_diagonal(ud): + """Transforms a matrix to the Bell basis and checks if it is diagonal.""" + ud_bell = np.dot(np.dot(np.conj(bell_basis).T, ud), bell_basis) + ud_diag = np.diag(ud_bell) + if not np.allclose(np.diag(ud_diag), ud_bell): # pragma: no cover + return None + uprod = np.prod(ud_diag) + if not np.allclose(uprod, 1): # pragma: no cover + return None + return ud_diag + + +def calculate_h_vector(ud_diag): + """Finds h parameters corresponding to exp(-iH). + + See Eq. (4)-(5) in arXiv:quant-ph/0307177. + """ + lambdas = -np.angle(ud_diag) + hx = (lambdas[0] + lambdas[2]) / 2.0 + hy = (lambdas[1] + lambdas[2]) / 2.0 + hz = (lambdas[0] + lambdas[1]) / 2.0 + return hx, hy, hz + + +def cnot_decomposition(q0, q1, hx, hy, hz): + """Performs decomposition (6) from arXiv:quant-ph/0307177.""" + u3 = -1j * (matrices.X + matrices.Z) / np.sqrt(2) + # use corrected version from PRA paper (not arXiv) + u2 = -u3 @ expm(-1j * (hx - np.pi / 4) * matrices.X) + # add an extra exp(-i pi / 4) global phase to get exact match + v2 = expm(-1j * hz * matrices.Z) * np.exp(-1j * np.pi / 4) + v3 = expm(1j * hy * matrices.Z) + w = (matrices.I - 1j * matrices.X) / np.sqrt(2) + # change CNOT to CZ using Hadamard gates + return [ + gates.H(q1), + gates.CZ(q0, q1), + gates.Unitary(u2, q0), + gates.Unitary(H @ v2 @ H, q1), + gates.CZ(q0, q1), + gates.Unitary(u3, q0), + gates.Unitary(H @ v3 @ H, q1), + gates.CZ(q0, q1), + gates.Unitary(w, q0), + gates.Unitary(np.conj(w).T @ H, q1), + ] + + +def cnot_decomposition_light(q0, q1, hx, hy): + """Performs decomposition (24) from arXiv:quant-ph/0307177.""" + w = (matrices.I - 1j * matrices.X) / np.sqrt(2) + u2 = expm(-1j * hx * matrices.X) + v2 = expm(1j * hy * matrices.Z) + # change CNOT to CZ using Hadamard gates + return [ + gates.Unitary(np.conj(w).T, q0), + gates.Unitary(H @ w, q1), + gates.CZ(q0, q1), + gates.Unitary(u2, q0), + gates.Unitary(H @ v2 @ H, q1), + gates.CZ(q0, q1), + gates.Unitary(w, q0), + gates.Unitary(np.conj(w).T @ H, q1), + ] + + +def two_qubit_decomposition(q0, q1, unitary): + """Performs two qubit unitary gate decomposition (24) from arXiv:quant-ph/0307177. + + Args: + q0, q1 (int): Target qubits + unitary (np.ndarray): Unitary 4x4 matrix we are decomposing. + + Returns: + list of gates implementing decomposition (24) from arXiv:quant-ph/0307177 + """ + ud_diag = to_bell_diagonal(unitary) + ud = None + if ud_diag is None: + u4, v4, ud, u1, v1 = magic_decomposition(unitary) + ud_diag = to_bell_diagonal(ud) + + hx, hy, hz = calculate_h_vector(ud_diag) + if np.allclose([hx, hy, hz], [0, 0, 0]): + gatelist = [gates.Unitary(u4 @ u1, q0), gates.Unitary(v4 @ v1, q1)] + elif np.allclose(hz, 0): + gatelist = cnot_decomposition_light(q0, q1, hx, hy) + if ud is None: + return gatelist + g0, g1 = gatelist[:2] + gatelist[0] = gates.Unitary(g0.parameters[0] @ u1, q0) + gatelist[1] = gates.Unitary(g1.parameters[0] @ v1, q1) + + g0, g1 = gatelist[-2:] + gatelist[-2] = gates.Unitary(u4 @ g0.parameters[0], q0) + gatelist[-1] = gates.Unitary(v4 @ g1.parameters[0], q1) + + else: + cnot_dec = cnot_decomposition(q0, q1, hx, hy, hz) + if ud is None: + return cnot_dec + + gatelist = [ + gates.Unitary(u1, q0), + gates.Unitary(H @ v1, q1), + ] + gatelist.extend(cnot_dec[1:]) + g0, g1 = gatelist[-2:] + gatelist[-2] = gates.Unitary(u4 @ g0.parameters[0], q0) + gatelist[-1] = gates.Unitary(v4 @ g1.parameters[0], q1) + + return gatelist diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py new file mode 100644 index 0000000000..d20139a393 --- /dev/null +++ b/src/qibo/transpiler/unroller.py @@ -0,0 +1,461 @@ +import numpy as np +from qibolab.native import NativeType + +from qibo import gates +from qibo.backends import NumpyBackend +from qibo.config import raise_error +from qibo.models import Circuit +from qibo.transpiler.abstract import Unroller +from qibo.transpiler.unitary_decompositions import ( + two_qubit_decomposition, + u3_decomposition, +) + +backend = NumpyBackend() + + +# TODO: Make setting single-qubit native gates more flexible +class NativeGates(Unroller): + """Translates a circuit to native gates. + + Args: + circuit (qibo.models.Circuit): circuit model to translate into native gates. + single_qubit_natives (Tuple): single qubit native gates. + two_qubit_natives (NativeType): two qubit native gates supported by the quantum hardware. + + Returns: + translated_circuit (qibo.models.Circuit): equivalent circuit with native gates. + """ + + def __init__( + self, + two_qubit_natives: NativeType, + single_qubit_natives=(gates.I, gates.Z, gates.RZ, gates.U3), + translate_single_qubit: bool = True, + ): + self.two_qubit_natives = two_qubit_natives + self.single_qubit_natives = single_qubit_natives + self.translate_single_qubit = translate_single_qubit + + def __call__(self, circuit: Circuit): + two_qubit_translated_circuit = circuit.__class__(circuit.nqubits) + translated_circuit = circuit.__class__(circuit.nqubits) + for gate in circuit.queue: + if len(gate.qubits) > 1 or self.translate_single_qubit: + two_qubit_translated_circuit.add( + translate_gate(gate, self.two_qubit_natives) + ) + else: + two_qubit_translated_circuit.add(gate) + if self.translate_single_qubit: + for gate in two_qubit_translated_circuit.queue: + if len(gate.qubits) == 1: + translated_circuit.add(translate_gate(gate, self.two_qubit_natives)) + else: + translated_circuit.add(gate) + else: + translated_circuit = two_qubit_translated_circuit + return translated_circuit + + +class DecompositionError(Exception): + """A decomposition error is raised when, during transpiling, gates are not correctly decomposed in native gates""" + + +def assert_decomposition( + circuit: Circuit, + two_qubit_natives: NativeType, + single_qubit_natives=(gates.I, gates.Z, gates.RZ, gates.U3), +): + """Checks if a circuit has been correctly decmposed into native gates. + + Args: + circuit (qibo.models.Circuit): circuit model to check. + """ + for gate in circuit.queue: + if isinstance(gate, gates.M): + continue + if len(gate.qubits) == 1: + if not isinstance(gate, single_qubit_natives): + raise DecompositionError( + f"{gate.name} is not a single qubit native gate." + ) + elif len(gate.qubits) == 2: + try: + native_type_gate = NativeType.from_gate(gate) + if not (native_type_gate in two_qubit_natives): + raise DecompositionError( + f"{gate.name} is not a two qubit native gate." + ) + except ValueError: + raise DecompositionError(f"{gate.name} is not a two qubit native gate.") + else: + raise DecompositionError(f"{gate.name} acts on more than two qubits.") + + +def translate_gate(gate, native_gates: NativeType): + """Maps Qibo gates to a hardware native implementation. + + Args: + gate (qibo.gates.abstract.Gate): gate to be decomposed. + native_gates (NativeType): two qubit native gates supported by the quantum hardware. + + Returns: + List of native gates + """ + if isinstance(gate, (gates.M, gates.I, gates.Align)): + return gate + + if len(gate.qubits) == 1: + return onequbit_dec(gate) + + if native_gates is NativeType.CZ | NativeType.iSWAP: + # Check for a special optimized decomposition. + if gate.__class__ in opt_dec.decompositions: + return opt_dec(gate) + # Check if the gate has a CZ decomposition + if not gate.__class__ in iswap_dec.decompositions: + return cz_dec(gate) + # Check the decomposition with less 2 qubit gates. + else: + if cz_dec.count_2q(gate) < iswap_dec.count_2q(gate): + return cz_dec(gate) + elif cz_dec.count_2q(gate) > iswap_dec.count_2q(gate): + return iswap_dec(gate) + # If equal check the decomposition with less 1 qubit gates. + # This is never used for now but may be useful for future generalization + elif cz_dec.count_1q(gate) < iswap_dec.count_1q(gate): # pragma: no cover + return cz_dec(gate) + else: # pragma: no cover + return iswap_dec(gate) + elif native_gates is NativeType.CZ: + return cz_dec(gate) + elif native_gates is NativeType.iSWAP: + if gate.__class__ in iswap_dec.decompositions: + return iswap_dec(gate) + else: + # First decompose into CZ + cz_decomposed = cz_dec(gate) + # Then CZ are decomposed into iSWAP + iswap_decomposed = [] + for g in cz_decomposed: + # Need recursive function as gates.Unitary is not in iswap_dec + for g_translated in translate_gate(g, NativeType.iSWAP): + iswap_decomposed.append(g_translated) + return iswap_decomposed + else: # pragma: no cover + raise_error(NotImplementedError, "Use only CZ and/or iSWAP as native gates") + + +class GateDecompositions: + """Abstract data structure that holds decompositions of gates.""" + + def __init__(self): + self.decompositions = {} + + def add(self, gate, decomposition): + """Register a decomposition for a gate.""" + self.decompositions[gate] = decomposition + + def count_2q(self, gate): + """Count the number of two-qubit gates in the decomposition of the given gate.""" + if gate.parameters: + decomposition = self.decompositions[gate.__class__](gate) + else: + decomposition = self.decompositions[gate.__class__] + return len(tuple(g for g in decomposition if len(g.qubits) > 1)) + + def count_1q(self, gate): + """Count the number of single qubit gates in the decomposition of the given gate.""" + if gate.parameters: + decomposition = self.decompositions[gate.__class__](gate) + else: + decomposition = self.decompositions[gate.__class__] + return len(tuple(g for g in decomposition if len(g.qubits) == 1)) + + def __call__(self, gate): + """Decompose a gate.""" + decomposition = self.decompositions[gate.__class__] + if callable(decomposition): + decomposition = decomposition(gate) + return [ + g.on_qubits({i: q for i, q in enumerate(gate.qubits)}) + for g in decomposition + ] + + +onequbit_dec = GateDecompositions() +onequbit_dec.add(gates.H, [gates.U3(0, 7 * np.pi / 2, np.pi, 0)]) +onequbit_dec.add(gates.X, [gates.U3(0, np.pi, 0, np.pi)]) +onequbit_dec.add(gates.Y, [gates.U3(0, np.pi, 0, 0)]) +# apply virtually by changing ``phase`` instead of using pulses +onequbit_dec.add(gates.Z, [gates.Z(0)]) +onequbit_dec.add(gates.S, [gates.RZ(0, np.pi / 2)]) +onequbit_dec.add(gates.SDG, [gates.RZ(0, -np.pi / 2)]) +onequbit_dec.add(gates.T, [gates.RZ(0, np.pi / 4)]) +onequbit_dec.add(gates.TDG, [gates.RZ(0, -np.pi / 4)]) +onequbit_dec.add( + gates.RX, lambda gate: [gates.U3(0, gate.parameters[0], -np.pi / 2, np.pi / 2)] +) +onequbit_dec.add(gates.RY, lambda gate: [gates.U3(0, gate.parameters[0], 0, 0)]) +# apply virtually by changing ``phase`` instead of using pulses +onequbit_dec.add(gates.RZ, lambda gate: [gates.RZ(0, gate.parameters[0])]) +# apply virtually by changing ``phase`` instead of using pulses +onequbit_dec.add(gates.GPI2, lambda gate: [gates.GPI2(0, gate.parameters[0])]) +# implemented as single RX90 pulse +onequbit_dec.add(gates.U1, lambda gate: [gates.RZ(0, gate.parameters[0])]) +onequbit_dec.add( + gates.U2, + lambda gate: [gates.U3(0, np.pi / 2, gate.parameters[0], gate.parameters[1])], +) +onequbit_dec.add( + gates.U3, + lambda gate: [ + gates.U3(0, gate.parameters[0], gate.parameters[1], gate.parameters[2]) + ], +) +onequbit_dec.add( + gates.Unitary, + lambda gate: [gates.U3(0, *u3_decomposition(gate.parameters[0]))], +) +onequbit_dec.add( + gates.FusedGate, + lambda gate: [gates.U3(0, *u3_decomposition(gate.matrix(backend)))], +) + +# register the iSWAP decompositions +iswap_dec = GateDecompositions() +iswap_dec.add( + gates.CNOT, + [ + gates.U3(0, 7 * np.pi / 2, np.pi, 0), + gates.U3(1, np.pi / 2, -np.pi, -np.pi), + gates.iSWAP(0, 1), + gates.U3(0, np.pi, 0, np.pi), + gates.U3(1, np.pi / 2, -np.pi, -np.pi), + gates.iSWAP(0, 1), + gates.U3(0, np.pi / 2, np.pi / 2, -np.pi), + gates.U3(1, np.pi / 2, -np.pi, -np.pi / 2), + ], +) +iswap_dec.add( + gates.CZ, + [ + gates.U3(0, 7 * np.pi / 2, np.pi, 0), + gates.U3(1, 7 * np.pi / 2, np.pi, 0), + gates.U3(1, np.pi / 2, -np.pi, -np.pi), + gates.iSWAP(0, 1), + gates.U3(0, np.pi, 0, np.pi), + gates.U3(1, np.pi / 2, -np.pi, -np.pi), + gates.iSWAP(0, 1), + gates.U3(0, np.pi / 2, np.pi / 2, -np.pi), + gates.U3(1, np.pi / 2, -np.pi, -np.pi / 2), + gates.U3(1, 7 * np.pi / 2, np.pi, 0), + ], +) +iswap_dec.add( + gates.SWAP, + [ + gates.iSWAP(0, 1), + gates.U3(1, np.pi / 2, -np.pi / 2, np.pi / 2), + gates.iSWAP(0, 1), + gates.U3(0, np.pi / 2, -np.pi / 2, np.pi / 2), + gates.iSWAP(0, 1), + gates.U3(1, np.pi / 2, -np.pi / 2, np.pi / 2), + ], +) +iswap_dec.add(gates.iSWAP, [gates.iSWAP(0, 1)]) + +# register CZ decompositions +cz_dec = GateDecompositions() +cz_dec.add(gates.CNOT, [gates.H(1), gates.CZ(0, 1), gates.H(1)]) +cz_dec.add(gates.CZ, [gates.CZ(0, 1)]) +cz_dec.add( + gates.SWAP, + [ + gates.H(1), + gates.CZ(0, 1), + gates.H(1), + gates.H(0), + gates.CZ(1, 0), + gates.H(0), + gates.H(1), + gates.CZ(0, 1), + gates.H(1), + ], +) +cz_dec.add( + gates.iSWAP, + [ + gates.U3(0, np.pi / 2.0, 0, -np.pi / 2.0), + gates.U3(1, np.pi / 2.0, 0, -np.pi / 2.0), + gates.CZ(0, 1), + gates.H(0), + gates.H(1), + gates.CZ(0, 1), + gates.H(0), + gates.H(1), + ], +) +cz_dec.add( + gates.CRX, + lambda gate: [ + gates.RX(1, gate.parameters[0] / 2.0), + gates.CZ(0, 1), + gates.RX(1, -gate.parameters[0] / 2.0), + gates.CZ(0, 1), + ], +) +cz_dec.add( + gates.CRY, + lambda gate: [ + gates.RY(1, gate.parameters[0] / 2.0), + gates.CZ(0, 1), + gates.RY(1, -gate.parameters[0] / 2.0), + gates.CZ(0, 1), + ], +) +cz_dec.add( + gates.CRZ, + lambda gate: [ + gates.RZ(1, gate.parameters[0] / 2.0), + gates.H(1), + gates.CZ(0, 1), + gates.RX(1, -gate.parameters[0] / 2.0), + gates.CZ(0, 1), + gates.H(1), + ], +) +cz_dec.add( + gates.CU1, + lambda gate: [ + gates.RZ(0, gate.parameters[0] / 2.0), + gates.H(1), + gates.CZ(0, 1), + gates.RX(1, -gate.parameters[0] / 2.0), + gates.CZ(0, 1), + gates.H(1), + gates.RZ(1, gate.parameters[0] / 2.0), + ], +) +cz_dec.add( + gates.CU2, + lambda gate: [ + gates.RZ(1, (gate.parameters[1] - gate.parameters[0]) / 2.0), + gates.H(1), + gates.CZ(0, 1), + gates.H(1), + gates.U3(1, -np.pi / 4, 0, -(gate.parameters[1] + gate.parameters[0]) / 2.0), + gates.H(1), + gates.CZ(0, 1), + gates.H(1), + gates.U3(1, np.pi / 4, gate.parameters[0], 0), + ], +) +cz_dec.add( + gates.CU3, + lambda gate: [ + gates.RZ(1, (gate.parameters[2] - gate.parameters[1]) / 2.0), + gates.H(1), + gates.CZ(0, 1), + gates.H(1), + gates.U3( + 1, + -gate.parameters[0] / 2.0, + 0, + -(gate.parameters[2] + gate.parameters[1]) / 2.0, + ), + gates.H(1), + gates.CZ(0, 1), + gates.H(1), + gates.U3(1, gate.parameters[0] / 2.0, gate.parameters[1], 0), + ], +) +cz_dec.add( + gates.FSWAP, + [ + gates.U3(0, np.pi / 2, -np.pi / 2, -np.pi), + gates.U3(1, np.pi / 2, np.pi / 2, np.pi / 2), + gates.CZ(0, 1), + gates.U3(0, np.pi / 2, 0, -np.pi / 2), + gates.U3(1, np.pi / 2, 0, np.pi / 2), + gates.CZ(0, 1), + gates.U3(0, np.pi / 2, np.pi / 2, -np.pi), + gates.U3(1, np.pi / 2, 0, -np.pi), + ], +) +cz_dec.add( + gates.RXX, + lambda gate: [ + gates.H(0), + gates.CZ(0, 1), + gates.RX(1, gate.parameters[0]), + gates.CZ(0, 1), + gates.H(0), + ], +) +cz_dec.add( + gates.RYY, + lambda gate: [ + gates.RX(0, np.pi / 2), + gates.U3(1, np.pi / 2, np.pi / 2, -np.pi), + gates.CZ(0, 1), + gates.RX(1, gate.parameters[0]), + gates.CZ(0, 1), + gates.RX(0, -np.pi / 2), + gates.U3(1, np.pi / 2, 0, np.pi / 2), + ], +) +cz_dec.add( + gates.RZZ, + lambda gate: [ + gates.H(1), + gates.CZ(0, 1), + gates.RX(1, gate.parameters[0]), + gates.CZ(0, 1), + gates.H(1), + ], +) +cz_dec.add( + gates.TOFFOLI, + [ + gates.CZ(1, 2), + gates.RX(2, -np.pi / 4), + gates.CZ(0, 2), + gates.RX(2, np.pi / 4), + gates.CZ(1, 2), + gates.RX(2, -np.pi / 4), + gates.CZ(0, 2), + gates.RX(2, np.pi / 4), + gates.RZ(1, np.pi / 4), + gates.H(1), + gates.CZ(0, 1), + gates.RZ(0, np.pi / 4), + gates.RX(1, -np.pi / 4), + gates.CZ(0, 1), + gates.H(1), + ], +) +cz_dec.add( + gates.Unitary, lambda gate: two_qubit_decomposition(0, 1, gate.parameters[0]) +) +cz_dec.add(gates.fSim, lambda gate: two_qubit_decomposition(0, 1, gate.matrix(backend))) +cz_dec.add( + gates.GeneralizedfSim, + lambda gate: two_qubit_decomposition(0, 1, gate.matrix(backend)), +) + + +# register other optimized gate decompositions +opt_dec = GateDecompositions() +opt_dec.add( + gates.SWAP, + [ + gates.H(0), + gates.SDG(0), + gates.SDG(1), + gates.iSWAP(0, 1), + gates.CZ(0, 1), + gates.H(1), + ], +) diff --git a/tests/test_transpilers_abstract.py b/tests/test_transpilers_abstract.py new file mode 100644 index 0000000000..2228fa4f98 --- /dev/null +++ b/tests/test_transpilers_abstract.py @@ -0,0 +1,23 @@ +import pytest + +from qibo import gates +from qibo.models import Circuit +from qibo.transpiler.abstract import find_gates_qubits_pairs + + +def test_circuit_representation(): + circuit = Circuit(5) + circuit.add(gates.CNOT(1, 0)) + circuit.add(gates.CNOT(2, 0)) + circuit.add(gates.X(1)) + circuit.add(gates.CZ(3, 0)) + circuit.add(gates.CNOT(4, 0)) + repr = find_gates_qubits_pairs(circuit) + assert repr == [[0, i + 1] for i in range(4)] + + +def test_circuit_representation_fail(): + circuit = Circuit(5) + circuit.add(gates.TOFFOLI(0, 1, 2)) + with pytest.raises(ValueError): + repr = find_gates_qubits_pairs(circuit) diff --git a/tests/test_transpilers_blocks.py b/tests/test_transpilers_blocks.py new file mode 100644 index 0000000000..1c44aa9c46 --- /dev/null +++ b/tests/test_transpilers_blocks.py @@ -0,0 +1,295 @@ +import numpy as np +import pytest + +from qibo import Circuit, gates +from qibo.transpiler.blocks import ( + Block, + BlockingError, + CircuitBlocks, + _find_previous_gates, + _find_successive_gates, + block_decomposition, + count_multi_qubit_gates, + gates_on_qubit, + initial_block_decomposition, + remove_gates, +) + + +def assert_gates_equality(gates_1: list, gates_2: list): + """Check that the gates are the same.""" + for g_1, g_2 in zip(gates_1, gates_2): + assert g_1.qubits == g_2.qubits + assert g_1.__class__ == g_2.__class__ + + +def test_count_2q_gates(): + block = Block(qubits=(0, 1), gates=[gates.CZ(0, 1), gates.CZ(0, 1), gates.H(0)]) + assert block.count_2q_gates() == 2 + + +def test_rename(): + block = Block(qubits=(0, 1), gates=[gates.CZ(0, 1)]) + block.rename("renamed_block") + assert block.name == "renamed_block" + + +def test_add_gate_and_entanglement(): + block = Block(qubits=(0, 1), gates=[gates.H(0)]) + assert block.entangled == False + block.add_gate(gates.CZ(0, 1)) + assert block.entangled == True + assert block.count_2q_gates() == 1 + + +def test_add_gate_error(): + block = Block(qubits=(0, 1), gates=[gates.CZ(0, 1)]) + with pytest.raises(BlockingError): + block.add_gate(gates.CZ(0, 2)) + + +def test_fuse_blocks(): + block_1 = Block(qubits=(0, 1), gates=[gates.CZ(0, 1)]) + block_2 = Block(qubits=(0, 1), gates=[gates.H(0)]) + fused = block_1.fuse(block_2) + assert_gates_equality(fused.gates, block_1.gates + block_2.gates) + + +def test_fuse_blocks_error(): + block_1 = Block(qubits=(0, 1), gates=[gates.CZ(0, 1)]) + block_2 = Block(qubits=(1, 2), gates=[gates.CZ(1, 2)]) + with pytest.raises(BlockingError): + fused = block_1.fuse(block_2) + + +@pytest.mark.parametrize("qubits", [(0, 1), (2, 1)]) +def test_commute_false(qubits): + block_1 = Block(qubits=(0, 1), gates=[gates.CZ(0, 1)]) + block_2 = Block(qubits=qubits, gates=[gates.CZ(*qubits)]) + assert block_1.commute(block_2) == False + + +def test_commute_true(): + block_1 = Block(qubits=(0, 1), gates=[gates.CZ(0, 1)]) + block_2 = Block(qubits=(2, 3), gates=[gates.CZ(2, 3)]) + assert block_1.commute(block_2) == True + + +def test_count_multi_qubit_gates(): + gatelist = [gates.CZ(0, 1), gates.H(0), gates.TOFFOLI(0, 1, 2)] + assert count_multi_qubit_gates(gatelist) == 2 + + +def test_gates_on_qubit(): + gatelist = [gates.H(0), gates.H(1), gates.H(2), gates.H(0)] + assert_gates_equality(gates_on_qubit(gatelist, 0), [gatelist[0], gatelist[-1]]) + assert_gates_equality(gates_on_qubit(gatelist, 1), [gatelist[1]]) + assert_gates_equality(gates_on_qubit(gatelist, 2), [gatelist[2]]) + + +def test_remove_gates(): + gatelist = [gates.H(0), gates.CZ(0, 1), gates.H(2), gates.CZ(0, 2)] + remaining = [gates.CZ(0, 1), gates.H(2)] + delete_list = [gatelist[0], gatelist[3]] + remove_gates(gatelist, delete_list) + assert_gates_equality(gatelist, remaining) + + +def test_find_previous_gates(): + gatelist = [gates.H(0), gates.H(1), gates.H(2)] + previous_gates = _find_previous_gates(gatelist, (0, 1)) + assert_gates_equality(previous_gates, gatelist[:2]) + + +def test_find_successive_gates(): + gatelist = [gates.H(0), gates.CZ(2, 3), gates.H(1), gates.H(2), gates.CZ(2, 1)] + successive_gates = _find_successive_gates(gatelist, (0, 1)) + assert_gates_equality(successive_gates, [gatelist[0], gatelist[2]]) + + +def test_initial_block_decomposition(): + circ = Circuit(5) + circ.add(gates.H(1)) + circ.add(gates.H(0)) + circ.add(gates.CZ(0, 1)) + circ.add(gates.CZ(0, 1)) + circ.add(gates.CZ(1, 2)) + circ.add(gates.H(3)) + circ.add(gates.H(4)) + blocks = initial_block_decomposition(circ) + assert_gates_equality(blocks[0].gates, [gates.H(1), gates.H(0), gates.CZ(0, 1)]) + assert len(blocks) == 4 + assert len(blocks[0].gates) == 3 + assert len(blocks[1].gates) == 1 + assert blocks[2].entangled == True + assert blocks[3].entangled == False + assert len(blocks[3].gates) == 2 + + +def test_initial_block_decomposition_error(): + circ = Circuit(3) + circ.add(gates.TOFFOLI(0, 1, 2)) + print(len(circ.queue[0].qubits)) + with pytest.raises(BlockingError): + blocks = initial_block_decomposition(circ) + + +def test_block_decomposition_error(): + circ = Circuit(1) + with pytest.raises(BlockingError): + block_decomposition(circ) + + +def test_block_decomposition_no_fuse(): + circ = Circuit(4) + circ.add(gates.H(1)) + circ.add(gates.H(0)) + circ.add(gates.CZ(0, 1)) + circ.add(gates.H(0)) + circ.add(gates.CZ(0, 1)) + circ.add(gates.CZ(1, 2)) + circ.add(gates.H(1)) + circ.add(gates.H(3)) + blocks = block_decomposition(circ, fuse=False) + assert_gates_equality( + blocks[0].gates, [gates.H(1), gates.H(0), gates.CZ(0, 1), gates.H(0)] + ) + assert len(blocks) == 4 + assert len(blocks[0].gates) == 4 + assert len(blocks[1].gates) == 1 + assert blocks[2].entangled == True + assert blocks[3].entangled == False + + +def test_block_decomposition(): + circ = Circuit(4) + circ.add(gates.H(1)) # first block + circ.add(gates.H(0)) # first block + circ.add(gates.CZ(0, 1)) # first block + circ.add(gates.H(0)) # first block + circ.add(gates.CZ(0, 1)) # first block + circ.add(gates.CZ(1, 2)) # second block + circ.add(gates.CZ(1, 2)) # second block + circ.add(gates.H(1)) # second block + circ.add(gates.H(3)) # 4 block + circ.add(gates.CZ(0, 1)) # 3 block + circ.add(gates.CZ(0, 1)) # 3 block + circ.add(gates.CZ(2, 3)) # 4 block + circ.add(gates.CZ(0, 1)) # 3 block + blocks = block_decomposition(circ) + assert_gates_equality( + blocks[0].gates, + [gates.H(1), gates.H(0), gates.CZ(0, 1), gates.H(0), gates.CZ(0, 1)], + ) + assert len(blocks) == 4 + assert blocks[0].count_2q_gates() == 2 + assert len(blocks[0].gates) == 5 + assert blocks[0].qubits == (0, 1) + assert blocks[1].count_2q_gates() == 2 + assert len(blocks[1].gates) == 3 + assert blocks[3].count_2q_gates() == 1 + assert len(blocks[3].gates) == 2 + assert blocks[3].qubits == (2, 3) + assert blocks[2].count_2q_gates() == 3 + assert len(blocks[2].gates) == 3 + + +def test_circuit_blocks(): + circ = Circuit(4) + circ.add(gates.H(1)) + circ.add(gates.H(0)) + circ.add(gates.CZ(0, 1)) + circ.add(gates.H(0)) + circ.add(gates.CZ(0, 1)) + circ.add(gates.CZ(1, 2)) + circ.add(gates.CZ(1, 2)) + circ.add(gates.H(1)) + circ.add(gates.H(3)) + circ.add(gates.CZ(0, 1)) + circ.add(gates.CZ(0, 1)) + circ.add(gates.CZ(2, 3)) + circ.add(gates.CZ(0, 1)) + circuit_blocks = CircuitBlocks(circ, index_names=True) + for index, block in enumerate(circuit_blocks()): + assert block.name == index + reconstructed_circ = circuit_blocks.circuit() + # Here we can't use assert gates_equality because the order of the gates is changed + np.testing.assert_allclose(circ(), reconstructed_circ()) + first_block = circuit_blocks.search_by_index(0) + assert_gates_equality( + first_block.gates, + [gates.H(1), gates.H(0), gates.CZ(0, 1), gates.H(0), gates.CZ(0, 1)], + ) + + +def test_add_block(): + circ = Circuit(4) + circ.add(gates.H(1)) + circ.add(gates.H(0)) + circ.add(gates.CZ(0, 1)) + circ.add(gates.H(0)) + circ.add(gates.CZ(0, 1)) + circ.add(gates.CZ(1, 2)) + circuit_blocks = CircuitBlocks(circ) + new_block = Block(qubits=(2, 3), gates=[gates.CZ(2, 3)]) + circ.add(gates.CZ(2, 3)) + circuit_blocks.add_block(new_block) + reconstructed_circ = circuit_blocks.circuit() + assert_gates_equality(reconstructed_circ.queue, circ.queue) + + +def test_add_block_error(): + circ = Circuit(2) + circ.add(gates.CZ(0, 1)) + circuit_blocks = CircuitBlocks(circ) + new_block = Block(qubits=(2, 3), gates=[gates.CZ(2, 3)]) + with pytest.raises(BlockingError): + circuit_blocks.add_block(new_block) + + +def test_remove_block(): + circ = Circuit(3) + circ.add(gates.CZ(0, 1)) + circ.add(gates.CZ(1, 2)) + circuit_blocks = CircuitBlocks(circ) + blocks = circuit_blocks() + circuit_blocks.remove_block(blocks[0]) + remaining_block = circuit_blocks() + assert_gates_equality(remaining_block[0].gates, [gates.CZ(1, 2)]) + + +def test_remove_block_error(): + circ = Circuit(3) + circ.add(gates.CZ(0, 1)) + circ.add(gates.CZ(1, 2)) + circuit_blocks = CircuitBlocks(circ) + new_block = Block(qubits=(2, 3), gates=[gates.CZ(2, 3)]) + with pytest.raises(BlockingError): + circuit_blocks.remove_block(new_block) + + +def test_search_by_index_error_no_indexes(): + circ = Circuit(2) + circ.add(gates.CZ(0, 1)) + circuit_blocks = CircuitBlocks(circ) + with pytest.raises(BlockingError): + circuit_blocks.search_by_index(0) + + +def test_search_by_index_error_no_index_found(): + circ = Circuit(2) + circ.add(gates.CZ(0, 1)) + circuit_blocks = CircuitBlocks(circ, index_names=True) + with pytest.raises(BlockingError): + circuit_blocks.search_by_index(1) + + +def test_block_on_qubits(): + block = Block( + qubits=(0, 1), gates=[gates.H(0), gates.CZ(0, 1), gates.H(1), gates.CZ(1, 0)] + ) + new_block = block.on_qubits(new_qubits=(2, 3)) + assert new_block.gates[0].qubits == (2,) + assert new_block.gates[1].qubits == (2, 3) + assert new_block.gates[2].qubits == (3,) + assert new_block.gates[3].qubits == (3, 2) diff --git a/tests/test_transpilers_optimizer.py b/tests/test_transpilers_optimizer.py new file mode 100644 index 0000000000..ad82e49911 --- /dev/null +++ b/tests/test_transpilers_optimizer.py @@ -0,0 +1,50 @@ +import networkx as nx +import pytest + +from qibo import gates +from qibo.models import Circuit +from qibo.transpiler.optimizer import Preprocessing, Rearrange + + +def star_connectivity(): + Q = ["q" + str(i) for i in range(5)] + chip = nx.Graph() + chip.add_nodes_from(Q) + graph_list = [(Q[i], Q[2]) for i in range(5) if i != 2] + chip.add_edges_from(graph_list) + return chip + + +def test_preprocessing_error(): + circ = Circuit(7) + preprocesser = Preprocessing(connectivity=star_connectivity()) + with pytest.raises(ValueError): + new_circuit = preprocesser(circuit=circ) + + +def test_preprocessing_same(): + circ = Circuit(5) + circ.add(gates.CNOT(0, 1)) + preprocesser = Preprocessing(connectivity=star_connectivity()) + new_circuit = preprocesser(circuit=circ) + assert new_circuit.ngates == 1 + + +def test_preprocessing_add(): + circ = Circuit(3) + circ.add(gates.CNOT(0, 1)) + preprocesser = Preprocessing(connectivity=star_connectivity()) + new_circuit = preprocesser(circuit=circ) + assert new_circuit.ngates == 1 + assert new_circuit.nqubits == 5 + + +def test_fusion(): + circuit = Circuit(2) + circuit.add(gates.X(0)) + circuit.add(gates.Z(0)) + circuit.add(gates.Y(0)) + circuit.add(gates.X(1)) + fusion = Rearrange(max_qubits=1) + fused_circ = fusion(circuit) + assert isinstance(fused_circ.queue[0], gates.Unitary) diff --git a/tests/test_transpilers_pipeline.py b/tests/test_transpilers_pipeline.py new file mode 100644 index 0000000000..f5facb2af7 --- /dev/null +++ b/tests/test_transpilers_pipeline.py @@ -0,0 +1,243 @@ +import itertools + +import networkx as nx +import numpy as np +import pytest +from qibolab.native import NativeType + +from qibo import gates +from qibo.models import Circuit +from qibo.transpiler.optimizer import Preprocessing +from qibo.transpiler.pipeline import ( + Passes, + TranspilerPipelineError, + assert_circuit_equivalence, + assert_transpiling, +) +from qibo.transpiler.placer import Random, ReverseTraversal, Trivial +from qibo.transpiler.router import ShortestPaths +from qibo.transpiler.unroller import NativeGates + + +def generate_random_circuit(nqubits, ngates, seed=None): + """Generate random circuits one-qubit rotations and CZ gates.""" + pairs = list(itertools.combinations(range(nqubits), 2)) + if seed is not None: # pragma: no cover + np.random.seed(seed) + + 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) + for _ in range(ngates): + igate = int(np.random.randint(0, n)) + if igate >= n1: + q = tuple(np.random.randint(0, nqubits, 2)) + while q[0] == q[1]: + q = tuple(np.random.randint(0, nqubits, 2)) + gate = two_qubit_gates[igate - n1] + else: + q = (np.random.randint(0, nqubits),) + gate = one_qubit_gates[igate] + if issubclass(gate, gates.ParametrizedGate): + theta = 2 * np.pi * np.random.random() + circuit.add(gate(*q, theta=theta)) + else: + circuit.add(gate(*q)) + return circuit + + +def small_circuit(): + circuit = Circuit(2) + circuit.add(gates.H(0)) + circuit.add(gates.CZ(0, 1)) + return circuit + + +def star_connectivity(): + Q = [i for i in range(5)] + chip = nx.Graph() + chip.add_nodes_from(Q) + graph_list = [(Q[i], Q[2]) for i in range(5) if i != 2] + chip.add_edges_from(graph_list) + return chip + + +@pytest.mark.parametrize("ngates", [5, 10, 50]) +def test_pipeline_default(ngates): + circ = generate_random_circuit(nqubits=5, ngates=ngates) + default_transpiler = Passes(connectivity=star_connectivity()) + transpiled_circ, final_layout = default_transpiler(circ) + initial_layout = default_transpiler.get_initial_layout() + assert_transpiling( + original_circuit=circ, + transpiled_circuit=transpiled_circ, + connectivity=star_connectivity(), + initial_layout=initial_layout, + final_layout=final_layout, + native_gates=NativeType.CZ, + check_circuit_equivalence=False, + ) + + +def test_assert_circuit_equivalence_equal(): + circ1 = Circuit(2) + circ2 = Circuit(2) + circ1.add(gates.X(0)) + circ1.add(gates.CZ(0, 1)) + circ2.add(gates.X(0)) + circ2.add(gates.CZ(0, 1)) + final_map = {"q0": 0, "q1": 1} + assert_circuit_equivalence(circ1, circ2, final_map=final_map) + + +def test_assert_circuit_equivalence_swap(): + circ1 = Circuit(2) + circ2 = Circuit(2) + circ1.add(gates.X(0)) + circ2.add(gates.SWAP(0, 1)) + circ2.add(gates.X(1)) + final_map = {"q0": 1, "q1": 0} + assert_circuit_equivalence(circ1, circ2, final_map=final_map) + + +def test_assert_circuit_equivalence_false(): + circ1 = Circuit(2) + circ2 = Circuit(2) + circ1.add(gates.X(0)) + circ2.add(gates.SWAP(0, 1)) + circ2.add(gates.X(1)) + final_map = {"q0": 0, "q1": 1} + with pytest.raises(TranspilerPipelineError): + assert_circuit_equivalence(circ1, circ2, final_map=final_map) + + +def test_assert_circuit_equivalence_wrong_nqubits(): + circ1 = Circuit(1) + circ2 = Circuit(2) + final_map = {"q0": 0, "q1": 1} + with pytest.raises(ValueError): + assert_circuit_equivalence(circ1, circ2, final_map=final_map) + + +def test_error_connectivity(): + with pytest.raises(TranspilerPipelineError): + default_transpiler = Passes() + + +def test_is_satisfied(): + default_transpiler = Passes(connectivity=star_connectivity()) + circuit = Circuit(5) + circuit.add(gates.CZ(0, 2)) + circuit.add(gates.Z(0)) + assert default_transpiler.is_satisfied(circuit) + + +def test_is_satisfied_false_decomposition(): + default_transpiler = Passes(connectivity=star_connectivity()) + circuit = Circuit(5) + circuit.add(gates.CZ(0, 2)) + circuit.add(gates.X(0)) + assert not default_transpiler.is_satisfied(circuit) + + +def test_is_satisfied_false_connectivity(): + default_transpiler = Passes(connectivity=star_connectivity()) + circuit = Circuit(5) + circuit.add(gates.CZ(0, 1)) + circuit.add(gates.Z(0)) + assert not default_transpiler.is_satisfied(circuit) + + +@pytest.mark.parametrize( + "circ", [generate_random_circuit(nqubits=5, ngates=20), small_circuit()] +) +def test_custom_passes(circ): + custom_passes = [] + custom_passes.append(Preprocessing(connectivity=star_connectivity())) + custom_passes.append(Random(connectivity=star_connectivity())) + custom_passes.append(ShortestPaths(connectivity=star_connectivity())) + custom_passes.append(NativeGates(two_qubit_natives=NativeType.iSWAP)) + custom_pipeline = Passes( + custom_passes, connectivity=star_connectivity(), native_gates=NativeType.iSWAP + ) + 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, + final_layout=final_layout, + native_gates=NativeType.iSWAP, + ) + + +@pytest.mark.parametrize( + "circ", [generate_random_circuit(nqubits=5, ngates=20), small_circuit()] +) +def test_custom_passes_reverse(circ): + custom_passes = [] + custom_passes.append(Preprocessing(connectivity=star_connectivity())) + custom_passes.append( + ReverseTraversal( + connectivity=star_connectivity(), + routing_algorithm=ShortestPaths(connectivity=star_connectivity()), + depth=20, + ) + ) + custom_passes.append(ShortestPaths(connectivity=star_connectivity())) + custom_passes.append(NativeGates(two_qubit_natives=NativeType.iSWAP)) + custom_pipeline = Passes( + custom_passes, connectivity=star_connectivity(), native_gates=NativeType.iSWAP + ) + 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, + final_layout=final_layout, + native_gates=NativeType.iSWAP, + ) + + +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=NativeType.CZ + ) + 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=NativeType.CZ + ) + circ = generate_random_circuit(nqubits=5, ngates=20) + with pytest.raises(TranspilerPipelineError): + transpiled_circ, final_layout = custom_pipeline(circ) + + +def test_custom_passes_wrong_pass(): + custom_passes = [0] + custom_pipeline = Passes(passes=custom_passes) + circ = generate_random_circuit(nqubits=5, ngates=5) + with pytest.raises(TranspilerPipelineError): + transpiled_circ, final_layout = custom_pipeline(circ) diff --git a/tests/test_transpilers_placer.py b/tests/test_transpilers_placer.py new file mode 100644 index 0000000000..748d469f0b --- /dev/null +++ b/tests/test_transpilers_placer.py @@ -0,0 +1,201 @@ +import networkx as nx +import pytest + +from qibo import gates +from qibo.models import Circuit +from qibo.transpiler.placer import ( + Custom, + PlacementError, + Random, + ReverseTraversal, + Subgraph, + Trivial, + assert_mapping_consistency, + assert_placement, +) +from qibo.transpiler.router import ShortestPaths + + +def star_connectivity(): + Q = [i for i in range(5)] + chip = nx.Graph() + chip.add_nodes_from(Q) + graph_list = [ + (Q[0], Q[2]), + (Q[1], Q[2]), + (Q[3], Q[2]), + (Q[4], Q[2]), + ] + chip.add_edges_from(graph_list) + return chip + + +def star_circuit(): + circuit = Circuit(5) + for i in range(1, 5): + circuit.add(gates.CNOT(i, 0)) + return circuit + + +def test_assert_placement_true(): + layout = {"q0": 0, "q1": 1, "q2": 2, "q3": 3, "q4": 4} + circuit = Circuit(5) + assert_placement(circuit, layout) + + +@pytest.mark.parametrize("qubits", [5, 3]) +@pytest.mark.parametrize( + "layout", [{"q0": 0, "q1": 1, "q2": 2, "q3": 3}, {"q0": 0, "q0": 1, "q2": 2}] +) +def test_assert_placement_false(qubits, layout): + circuit = Circuit(qubits) + with pytest.raises(PlacementError): + assert_placement(circuit, layout) + + +def test_mapping_consistency_true(): + layout = {"q0": 0, "q1": 2, "q2": 1, "q3": 4, "q4": 3} + assert_mapping_consistency(layout) + + +@pytest.mark.parametrize( + "layout", + [ + {"q0": 0, "q1": 0, "q2": 1, "q3": 4, "q4": 3}, + {"q0": 0, "q1": 2, "q0": 1, "q3": 4, "q4": 3}, + ], +) +def test_mapping_consistency_false(layout): + with pytest.raises(PlacementError): + assert_mapping_consistency(layout) + + +def test_trivial(): + circuit = Circuit(5) + connectivity = star_connectivity() + placer = Trivial(connectivity=connectivity) + layout = placer(circuit) + assert layout == {"q0": 0, "q1": 1, "q2": 2, "q3": 3, "q4": 4} + assert_placement(circuit, layout) + + +def test_trivial_error(): + circuit = Circuit(4) + connectivity = star_connectivity() + placer = Trivial(connectivity=connectivity) + with pytest.raises(PlacementError): + layout = placer(circuit) + + +@pytest.mark.parametrize( + "custom_layout", [[4, 3, 2, 1, 0], {"q0": 4, "q1": 3, "q2": 2, "q3": 1, "q4": 0}] +) +@pytest.mark.parametrize("give_circuit", [True, False]) +def test_custom(custom_layout, give_circuit): + if give_circuit: + circuit = Circuit(5) + else: + circuit = None + connectivity = star_connectivity() + placer = Custom(connectivity=connectivity, map=custom_layout) + layout = placer(circuit) + assert layout == {"q0": 4, "q1": 3, "q2": 2, "q3": 1, "q4": 0} + + +def test_custom_error_circuit(): + circuit = Circuit(3) + custom_layout = [4, 3, 2, 1, 0] + connectivity = star_connectivity() + placer = Custom(connectivity=connectivity, map=custom_layout) + with pytest.raises(PlacementError): + layout = placer(circuit) + + +def test_custom_error_no_circuit(): + connectivity = star_connectivity() + custom_layout = {"q0": 4, "q1": 3, "q2": 2, "q3": 0, "q4": 0} + placer = Custom(connectivity=connectivity, map=custom_layout) + with pytest.raises(PlacementError): + layout = placer() + + +def test_custom_error_type(): + circuit = Circuit(5) + connectivity = star_connectivity() + layout = 1 + placer = Custom(connectivity=connectivity, map=layout) + with pytest.raises(TypeError): + layout = 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) + + +def imperfect_circuit(): + circuit = Circuit(5) + circuit.add(gates.CNOT(1, 3)) + circuit.add(gates.CNOT(2, 4)) + circuit.add(gates.CNOT(2, 1)) + circuit.add(gates.CNOT(4, 3)) + circuit.add(gates.CNOT(3, 2)) + circuit.add(gates.CNOT(2, 1)) + circuit.add(gates.CNOT(4, 3)) + circuit.add(gates.CNOT(1, 2)) + circuit.add(gates.CNOT(3, 1)) + return circuit + + +def test_subgraph_non_perfect(): + connectivity = star_connectivity() + placer = Subgraph(connectivity=connectivity) + layout = placer(imperfect_circuit()) + assert_placement(imperfect_circuit(), layout) + + +def test_subgraph_error(): + connectivity = star_connectivity() + placer = Subgraph(connectivity=connectivity) + circuit = Circuit(5) + with pytest.raises(ValueError): + layout = placer(circuit) + + +@pytest.mark.parametrize("reps", [1, 10, 100]) +def test_random(reps): + connectivity = star_connectivity() + 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) + + +@pytest.mark.parametrize("gates", [None, 5, 13]) +def test_reverse_traversal(gates): + circuit = star_circuit() + connectivity = star_connectivity() + routing = ShortestPaths(connectivity=connectivity) + placer = ReverseTraversal(connectivity, routing, depth=gates) + layout = placer(circuit) + assert_placement(circuit, layout) + + +def test_reverse_traversal_no_gates(): + connectivity = star_connectivity() + routing = ShortestPaths(connectivity=connectivity) + placer = ReverseTraversal(connectivity, routing, depth=10) + circuit = Circuit(5) + with pytest.raises(ValueError): + layout = placer(circuit) diff --git a/tests/test_transpilers_router.py b/tests/test_transpilers_router.py new file mode 100644 index 0000000000..3219acb5c2 --- /dev/null +++ b/tests/test_transpilers_router.py @@ -0,0 +1,329 @@ +import networkx as nx +import numpy as np +import pytest + +from qibo import gates +from qibo.models import Circuit +from qibo.transpiler.optimizer import Preprocessing +from qibo.transpiler.pipeline import assert_circuit_equivalence +from qibo.transpiler.placer import ( + Custom, + PlacementError, + Random, + Subgraph, + Trivial, + assert_placement, +) +from qibo.transpiler.router import ( + CircuitMap, + ConnectivityError, + Sabre, + ShortestPaths, + assert_connectivity, +) + + +def star_connectivity(): + Q = [i for i in range(5)] + chip = nx.Graph() + chip.add_nodes_from(Q) + graph_list = [(Q[i], Q[2]) for i in range(5) if i != 2] + chip.add_edges_from(graph_list) + return chip + + +def grid_connectivity(): + Q = [i for i in range(5)] + chip = nx.Graph() + chip.add_nodes_from(Q) + graph_list = [(Q[0], Q[1]), (Q[1], Q[2]), (Q[2], Q[3]), (Q[3], Q[0]), (Q[0], Q[4])] + chip.add_edges_from(graph_list) + return chip + + +def generate_random_circuit(nqubits, ngates, 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] + n1, n2 = len(one_qubit_gates), len(two_qubit_gates) + n = n1 + n2 if nqubits > 1 else n1 + circuit = Circuit(nqubits) + for _ in range(ngates): + igate = int(np.random.randint(0, n)) + if igate >= n1: + q = tuple(np.random.randint(0, nqubits, 2)) + while q[0] == q[1]: + q = tuple(np.random.randint(0, nqubits, 2)) + gate = two_qubit_gates[igate - n1] + else: + q = (np.random.randint(0, nqubits),) + gate = one_qubit_gates[igate] + if issubclass(gate, gates.ParametrizedGate): + theta = 2 * np.pi * np.random.random() + circuit.add(gate(*q, theta=theta, trainable=False)) + else: + circuit.add(gate(*q)) + return circuit + + +def star_circuit(): + circuit = Circuit(5) + for i in range(1, 5): + circuit.add(gates.CNOT(i, 0)) + return circuit + + +def matched_circuit(): + """Return a simple circuit that can be executed on star connectivity""" + circuit = Circuit(5) + circuit.add(gates.CZ(0, 2)) + circuit.add(gates.CZ(1, 2)) + circuit.add(gates.Z(1)) + circuit.add(gates.CZ(2, 1)) + circuit.add(gates.M(0)) + return circuit + + +def test_assert_connectivity(): + assert_connectivity(star_connectivity(), matched_circuit()) + + +def test_assert_connectivity_false(): + circuit = Circuit(5) + circuit.add(gates.CZ(0, 1)) + with pytest.raises(ConnectivityError): + assert_connectivity(star_connectivity(), circuit) + + +def test_assert_connectivity_3q(): + circuit = Circuit(5) + circuit.add(gates.TOFFOLI(0, 1, 2)) + with pytest.raises(ConnectivityError): + assert_connectivity(star_connectivity(), circuit) + + +@pytest.mark.parametrize("split", [2.0, -1.0]) +def test_split_setter(split): + with pytest.raises(ValueError): + transpiler = ShortestPaths( + connectivity=star_connectivity(), sampling_split=split + ) + + +def test_insufficient_qubits(): + circuit = generate_random_circuit(10, 20) + placer = Trivial() + initial_layout = placer(circuit) + transpiler = ShortestPaths(connectivity=star_connectivity()) + with pytest.raises(ValueError): + transpiler(circuit, initial_layout) + + +def test_incorrect_initial_layout(): + placer = Trivial() + circuit = Circuit(4) + circuit.add(gates.CNOT(1, 0)) + circuit.add(gates.CNOT(2, 0)) + circuit.add(gates.CNOT(3, 0)) + initial_layout = placer(circuit) + transpiler = ShortestPaths(connectivity=star_connectivity()) + with pytest.raises(PlacementError): + transpiler(circuit, initial_layout) + + +@pytest.mark.parametrize("gates", [10, 30]) +@pytest.mark.parametrize("qubits", [3, 5]) +@pytest.mark.parametrize("placer", [Trivial, Random]) +@pytest.mark.parametrize("connectivity", [star_connectivity(), grid_connectivity()]) +def test_random_circuits_5q(gates, qubits, placer, connectivity): + placer = placer(connectivity=connectivity) + layout_circ = Circuit(5) + initial_layout = placer(layout_circ) + transpiler = ShortestPaths(connectivity=connectivity, verbose=True) + circuit = generate_random_circuit(nqubits=qubits, ngates=gates) + transpiled_circuit, final_qubit_map = transpiler(circuit, initial_layout) + assert transpiler.added_swaps >= 0 + assert_connectivity(connectivity, transpiled_circuit) + assert_placement(transpiled_circuit, final_qubit_map) + assert gates + transpiler.added_swaps == transpiled_circuit.ngates + qubit_matcher = Preprocessing(connectivity=connectivity) + new_circuit = qubit_matcher(circuit=circuit) + assert_circuit_equivalence( + original_circuit=new_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) + assert transpiler.added_swaps == 0 + assert_connectivity(star_connectivity(), transpiled_circuit) + assert_placement(transpiled_circuit, final_qubit_map) + 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(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) + assert transpiler.added_swaps == 1 + assert_connectivity(star_connectivity(), transpiled_circuit) + assert_placement(transpiled_circuit, final_qubit_map) + 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()) + 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) + assert transpiled_circuit.ngates == 3 + measured_qubits = transpiled_circuit.queue[2].qubits + assert measured_qubits == (0, 1, 3) + assert_circuit_equivalence( + original_circuit=circuit, + transpiled_circuit=transpiled_circuit, + final_map=final_qubit_map, + initial_map=initial_layout, + ) + + +def test_circuit_map(): + circ = Circuit(4) + circ.add(gates.H(1)) + circ.add(gates.H(0)) + circ.add(gates.CZ(0, 1)) + circ.add(gates.H(0)) + 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) + block_list = circuit_map.circuit_blocks + # test blocks_qubits_pairs + assert circuit_map.blocks_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() + assert isinstance(routed_circuit.queue[0], gates.H) + assert len(routed_circuit.queue) == 4 + assert routed_circuit.queue[2].qubits == (1, 2) + # test update + circuit_map.update((0, 2)) + routed_circuit = circuit_map.routed_circuit() + assert isinstance(routed_circuit.queue[4], gates.SWAP) + assert routed_circuit.queue[4].qubits == (1, 0) + assert circuit_map._swaps == 1 + assert circuit_map._circuit_logical == [2, 1, 0, 3] + circuit_map.update((1, 2)) + routed_circuit = circuit_map.routed_circuit() + assert routed_circuit.queue[5].qubits == (2, 0) + assert circuit_map._circuit_logical == [1, 2, 0, 3] + # 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() + assert isinstance(routed_circuit.queue[6], gates.CZ) + # circuit to logical map: [1,2,0,3]. initial map: {"q0": 2, "q1": 0, "q2": 1, "q3": 3}. + assert routed_circuit.queue[6].qubits == (0, 1) # initial circuit qubits (1,2) + assert routed_circuit.queue[7].qubits == (2, 0) # (0,1) + assert routed_circuit.queue[8].qubits == (1, 3) # (2,3) + assert len(circuit_map.circuit_blocks()) == 0 + # test final layout + assert circuit_map.final_layout() == {"q0": 1, "q1": 2, "q2": 0, "q3": 3} + + +def test_sabre_matched(): + placer = Trivial() + layout_circ = Circuit(5) + initial_layout = placer(layout_circ) + router = Sabre(connectivity=star_connectivity()) + routed_circuit, final_map = router( + circuit=matched_circuit(), initial_layout=initial_layout + ) + 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_circuit_equivalence( + original_circuit=matched_circuit(), + transpiled_circuit=routed_circuit, + final_map=final_map, + initial_map=initial_layout, + ) + + +def test_sabre_simple(): + placer = Trivial() + circ = Circuit(5) + circ.add(gates.CZ(0, 1)) + initial_layout = placer(circ) + router = Sabre(connectivity=star_connectivity()) + routed_circuit, final_map = router(circuit=circ, initial_layout=initial_layout) + 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_circuit_equivalence( + original_circuit=circ, + transpiled_circuit=routed_circuit, + final_map=final_map, + initial_map=initial_layout, + ) + + +@pytest.mark.parametrize("gates", [10, 40]) +@pytest.mark.parametrize("look", [0, 5]) +@pytest.mark.parametrize("decay", [0.5, 1.0]) +@pytest.mark.parametrize("placer", [Trivial, Random]) +@pytest.mark.parametrize("connectivity", [star_connectivity(), grid_connectivity()]) +def test_sabre_random_circuits(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=decay) + circuit = generate_random_circuit(nqubits=5, ngates=gates) + transpiled_circuit, final_qubit_map = router(circuit, initial_layout) + assert router.added_swaps >= 0 + assert_connectivity(connectivity, transpiled_circuit) + assert_placement(transpiled_circuit, final_qubit_map) + assert gates + router.added_swaps == transpiled_circuit.ngates + assert_circuit_equivalence( + original_circuit=circuit, + transpiled_circuit=transpiled_circuit, + final_map=final_qubit_map, + initial_map=initial_layout, + ) + + +def test_sabre_memory_map(): + placer = Trivial() + layout_circ = Circuit(5) + initial_layout = placer(layout_circ) + router = Sabre(connectivity=star_connectivity()) + router.preprocessing(circuit=star_circuit(), initial_layout=initial_layout) + router._memory_map = [[1, 0, 2, 3, 4]] + value = router.compute_cost((0, 1)) + assert value == float("inf") diff --git a/tests/test_transpilers_star_connectivity.py b/tests/test_transpilers_star_connectivity.py new file mode 100644 index 0000000000..e963a00116 --- /dev/null +++ b/tests/test_transpilers_star_connectivity.py @@ -0,0 +1,90 @@ +import itertools + +import numpy as np +import pytest + +from qibo import gates +from qibo.backends import NumpyBackend +from qibo.models import Circuit +from qibo.transpiler.pipeline import transpose_qubits +from qibo.transpiler.star_connectivity import StarConnectivity + + +def generate_random_circuit(nqubits, depth, seed=None, middle_qubit=2): + """Generate random circuits one-qubit rotations and CZ gates.""" + # find the number of qubits for hardware circuit + if nqubits == 1: + hardware_qubits = 1 + else: + hardware_qubits = max(nqubits, middle_qubit + 1) + + pairs = list(itertools.combinations(range(hardware_qubits), 2)) + if seed is not None: # pragma: no cover + np.random.seed(seed) + + rotations = [gates.RX, gates.RY, gates.RZ] + circuit = Circuit(hardware_qubits) + for _ in range(depth): + for i in range(hardware_qubits): + # generate a random rotation + rotation = rotations[int(np.random.randint(0, 3))] + theta = 2 * np.pi * np.random.random() + circuit.add(rotation(i, theta=theta)) + # add CZ gates on random qubit pairs + for i in np.random.randint(0, len(pairs), len(pairs)): + q1, q2 = pairs[i] + circuit.add(gates.CZ(q1, q2)) + + return circuit + + +@pytest.mark.parametrize("verbose", [True, False]) +@pytest.mark.parametrize("nqubits", [1, 2, 3, 4, 5]) +@pytest.mark.parametrize("middle_qubit", [0, 1, 2, 3, 4]) +@pytest.mark.parametrize("depth", [2, 10]) +def test_fix_connectivity(nqubits, depth, middle_qubit, verbose): + """Checks that the transpiled circuit can be executed and is equivalent to original.""" + original = generate_random_circuit(nqubits, depth, middle_qubit=middle_qubit) + transpiler = StarConnectivity(middle_qubit=middle_qubit, verbose=verbose) + transpiled, hardware_qubits = transpiler(original) + # check that transpiled circuit can be executed + assert transpiler.is_satisfied(transpiled) + # check that execution results agree with original (using simulation) + backend = NumpyBackend() + final_state = backend.execute_circuit(transpiled).state() + target_state = backend.execute_circuit(original).state() + hardware_qubits = list(hardware_qubits.values()) + target_state = transpose_qubits(target_state, hardware_qubits) + np.testing.assert_allclose(final_state, target_state) + + +@pytest.mark.parametrize("nqubits", [2, 3, 4, 5]) +@pytest.mark.parametrize("middle_qubit", [0, 1, 2, 3, 4]) +@pytest.mark.parametrize("unitary_dim", [1, 2]) +@pytest.mark.parametrize("depth", [2, 10]) +def test_fix_connectivity_unitaries(nqubits, unitary_dim, depth, middle_qubit): + """Checks that the transpiled circuit can be executed and is equivalent to original + when using unitaries.""" + + from .test_transpilers_unitary_decompositions import random_unitary + + # find the number of qubits for hardware circuit + n_hardware_qubits = max(nqubits, middle_qubit + 1) + + original = Circuit(n_hardware_qubits) + pairs = list(itertools.combinations(range(n_hardware_qubits), unitary_dim)) + for _ in range(depth): + qubits = pairs[int(np.random.randint(len(pairs)))] + original.add(gates.Unitary(random_unitary(unitary_dim), *qubits)) + + transpiler = StarConnectivity(middle_qubit=middle_qubit) + transpiled, hardware_qubits = transpiler(original) + # check that transpiled circuit can be executed + assert transpiler.is_satisfied(transpiled) + # check that execution results agree with original (using simulation) + backend = NumpyBackend() + final_state = backend.execute_circuit(transpiled).state() + target_state = backend.execute_circuit(original).state() + hardware_qubits = list(hardware_qubits.values()) + target_state = transpose_qubits(target_state, hardware_qubits) + np.testing.assert_allclose(final_state, target_state) diff --git a/tests/test_transpilers_unitary_decompositions.py b/tests/test_transpilers_unitary_decompositions.py new file mode 100644 index 0000000000..47f46dc901 --- /dev/null +++ b/tests/test_transpilers_unitary_decompositions.py @@ -0,0 +1,189 @@ +import numpy as np +import pytest +from scipy.linalg import expm + +from qibo import gates, matrices +from qibo.backends import NumpyBackend +from qibo.models import Circuit +from qibo.transpiler.unitary_decompositions import ( + bell_basis, + calculate_h_vector, + calculate_psi, + calculate_single_qubit_unitaries, + cnot_decomposition, + cnot_decomposition_light, + magic_basis, + magic_decomposition, + to_bell_diagonal, + two_qubit_decomposition, +) + +NREPS = 5 # number of repetitions to execute random tests +ATOL = 1e-12 + + +def random_state(nqubits): + shape = 2**nqubits + psi = np.random.random(shape) + 1j * np.random.random(shape) + return psi / np.sqrt(np.sum(np.abs(psi) ** 2)) + + +def random_unitary(nqubits): + """Generates a random unitary matrix acting on nqubits.""" + shape = 2 * (2**nqubits,) + m = np.random.random(shape) + 1j * np.random.random(shape) + return expm(1j * (m + np.conj(m.T))) + + +def bell_unitary(hx, hy, hz): + ham = ( + hx * np.kron(matrices.X, matrices.X) + + hy * np.kron(matrices.Y, matrices.Y) + + hz * np.kron(matrices.Z, matrices.Z) + ) + return expm(-1j * ham) + + +def purity(state): + """Calculates the purity of the partial trace of a two-qubit state.""" + mat = np.reshape(state, (2, 2)) + reduced_rho = np.dot(mat, np.conj(mat.T)) + return np.trace(np.dot(reduced_rho, reduced_rho)) + + +def assert_single_qubits(psi, ua, ub): + """Assert UA, UB map the maximally entangled basis ``psi`` to the magic basis.""" + uaub = np.kron(ua, ub) + for i, j in zip(range(4), [0, 1, 3, 2]): + final_state = np.dot(uaub, psi[:, i]) + target_state = magic_basis[:, j] + fidelity = np.abs(np.dot(np.conj(target_state), final_state)) + np.testing.assert_allclose(fidelity, 1) + # np.testing.assert_allclose(final_state, target_state, atol=1e-12) + + +def test_u3_decomposition(): + backend = NumpyBackend() + theta, phi, lam = 0.1, 0.2, 0.3 + u3_matrix = gates.U3(0, theta, phi, lam).matrix(backend) + rz1 = gates.RZ(0, phi).matrix(backend) + rz2 = gates.RZ(0, theta).matrix(backend) + rz3 = gates.RZ(0, lam).matrix(backend) + rx1 = gates.RX(0, -np.pi / 2).matrix(backend) + rx2 = gates.RX(0, np.pi / 2).matrix(backend) + target_matrix = rz1 @ rx1 @ rz2 @ rx2 @ rz3 + np.testing.assert_allclose(u3_matrix, target_matrix) + + +@pytest.mark.parametrize("run_number", range(NREPS)) +def test_eigenbasis_entanglement(run_number): + """Check that the eigenvectors of UT_U are maximally entangled.""" + unitary = random_unitary(2) + states, eigvals = calculate_psi(unitary) + np.testing.assert_allclose(np.abs(eigvals), np.ones(4)) + for state in states.T: + np.testing.assert_allclose(purity(state), 0.5) + + +@pytest.mark.parametrize("run_number", range(NREPS)) +def test_v_decomposition(run_number): + """Check that V_A V_B |psi_k> = |phi_k> according to Lemma 1.""" + unitary = random_unitary(2) + psi, eigvals = calculate_psi(unitary) + va, vb = calculate_single_qubit_unitaries(psi) + assert_single_qubits(psi, va, vb) + + +@pytest.mark.parametrize("run_number", range(NREPS)) +def test_u_decomposition(run_number): + r"""Check that U_A\dagger U_B\dagger |psi_k tilde> = |phi_k> according to Lemma 1.""" + unitary = random_unitary(2) + psi, eigvals = calculate_psi(unitary) + psi_tilde = np.conj(np.sqrt(eigvals)) * np.dot(unitary, psi) + ua_dagger, ub_dagger = calculate_single_qubit_unitaries(psi_tilde) + assert_single_qubits(psi_tilde, ua_dagger, ub_dagger) + + +@pytest.mark.parametrize("run_number", range(NREPS)) +def test_ud_eigenvalues(run_number): + """Check that U_d is diagonal in the Bell basis.""" + unitary = random_unitary(2) + ua, ub, ud, va, vb = magic_decomposition(unitary) + + unitary_recon = np.kron(ua, ub) @ ud @ np.kron(va, vb) + np.testing.assert_allclose(unitary_recon, unitary) + + ud_bell = np.dot(np.dot(np.conj(bell_basis).T, ud), bell_basis) + ud_diag = np.diag(ud_bell) + np.testing.assert_allclose(np.diag(ud_diag), ud_bell, atol=ATOL) + np.testing.assert_allclose(np.prod(ud_diag), 1) + + +@pytest.mark.parametrize("run_number", range(NREPS)) +def test_calculate_h_vector(run_number): + unitary = random_unitary(2) + ua, ub, ud, va, vb = magic_decomposition(unitary) + ud_diag = to_bell_diagonal(ud) + assert ud_diag is not None + hx, hy, hz = calculate_h_vector(ud_diag) + target_matrix = bell_unitary(hx, hy, hz) + np.testing.assert_allclose(ud, target_matrix, atol=ATOL) + + +@pytest.mark.parametrize("run_number", range(NREPS)) +def test_cnot_decomposition(run_number): + hx, hy, hz = np.random.random(3) + target_matrix = bell_unitary(hx, hy, hz) + c = Circuit(2) + c.add(cnot_decomposition(0, 1, hx, hy, hz)) + final_matrix = c.unitary(NumpyBackend()) + np.testing.assert_allclose(final_matrix, target_matrix, atol=ATOL) + + +@pytest.mark.parametrize("run_number", range(NREPS)) +def test_cnot_decomposition_light(run_number): + hx, hy = np.random.random(2) + target_matrix = bell_unitary(hx, hy, 0) + c = Circuit(2) + c.add(cnot_decomposition_light(0, 1, hx, hy)) + final_matrix = c.unitary(NumpyBackend()) + np.testing.assert_allclose(final_matrix, target_matrix, atol=ATOL) + + +@pytest.mark.parametrize("run_number", range(NREPS)) +def test_two_qubit_decomposition(run_number): + backend = NumpyBackend() + unitary = random_unitary(2) + c = Circuit(2) + c.add(two_qubit_decomposition(0, 1, unitary)) + final_matrix = c.unitary(backend) + np.testing.assert_allclose(final_matrix, unitary, atol=ATOL) + + +@pytest.mark.parametrize("gatename", ["CNOT", "CZ", "SWAP", "iSWAP", "fSim"]) +def test_two_qubit_decomposition_common_gates(gatename): + """Test general two-qubit decomposition on some common gates.""" + backend = NumpyBackend() + if gatename == "fSim": + gate = gates.fSim(0, 1, theta=0.1, phi=0.2) + else: + gate = getattr(gates, gatename)(0, 1) + matrix = gate.matrix(backend) + c = Circuit(2) + c.add(two_qubit_decomposition(0, 1, matrix)) + final_matrix = c.unitary(backend) + np.testing.assert_allclose(final_matrix, matrix, atol=ATOL) + + +@pytest.mark.parametrize("run_number", range(NREPS)) +@pytest.mark.parametrize("hz_zero", [False, True]) +def test_two_qubit_decomposition_bell_unitary(run_number, hz_zero): + backend = NumpyBackend() + hx, hy, hz = (2 * np.random.random(3) - 1) * np.pi + if hz_zero: + hz = 0 + unitary = bell_unitary(hx, hy, hz) + c = Circuit(2) + c.add(two_qubit_decomposition(0, 1, unitary)) + final_matrix = c.unitary(backend) + np.testing.assert_allclose(final_matrix, unitary, atol=ATOL) diff --git a/tests/test_transpilers_unroller.py b/tests/test_transpilers_unroller.py new file mode 100644 index 0000000000..23b234cf32 --- /dev/null +++ b/tests/test_transpilers_unroller.py @@ -0,0 +1,233 @@ +import numpy as np +import pytest +from qibolab.native import NativeType + +from qibo import gates +from qibo.backends import NumpyBackend +from qibo.models import Circuit +from qibo.transpiler.unroller import ( + DecompositionError, + NativeGates, + assert_decomposition, + translate_gate, +) + + +def assert_matrices_allclose(gate, two_qubit_natives): + backend = NumpyBackend() + native_gates = translate_gate(gate, two_qubit_natives) + target_matrix = gate.matrix(backend) + # Remove global phase from target matrix + target_unitary = target_matrix / np.power( + np.linalg.det(target_matrix), 1 / float(target_matrix.shape[0]), dtype=complex + ) + circuit = Circuit(len(gate.qubits)) + circuit.add(translate_gate(gate, two_qubit_natives)) + native_matrix = circuit.unitary(backend) + # Remove global phase from native matrix + native_unitary = native_matrix / np.power( + np.linalg.det(native_matrix), 1 / float(native_matrix.shape[0]), dtype=complex + ) + # There can still be phase differences of -1, -1j, 1j + c = 0 + for phase in [1, -1, 1j, -1j]: + if np.allclose(phase * native_unitary, target_unitary, atol=1e-12): + c = 1 + np.testing.assert_allclose(c, 1) + + +@pytest.mark.parametrize("gatename", ["H", "X", "Y", "I"]) +def test_pauli_to_native(gatename): + gate = getattr(gates, gatename)(0) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ) + + +@pytest.mark.parametrize("gatename", ["RX", "RY", "RZ"]) +def test_rotations_to_native(gatename): + gate = getattr(gates, gatename)(0, theta=0.1) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ) + + +@pytest.mark.parametrize("gatename", ["S", "SDG", "T", "TDG"]) +def test_special_single_qubit_to_native(gatename): + gate = getattr(gates, gatename)(0) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ) + + +def test_u1_to_native(): + gate = gates.U1(0, theta=0.5) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ) + + +def test_u2_to_native(): + gate = gates.U2(0, phi=0.1, lam=0.3) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ) + + +def test_u3_to_native(): + gate = gates.U3(0, theta=0.2, phi=0.1, lam=0.3) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ) + + +def test_gpi2_to_native(): + gate = gates.GPI2(0, phi=0.123) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ) + + +@pytest.mark.parametrize("gatename", ["CNOT", "CZ", "SWAP", "iSWAP", "FSWAP"]) +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +def test_two_qubit_to_native(gatename, natives): + gate = getattr(gates, gatename)(0, 1) + assert_matrices_allclose(gate, natives) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +@pytest.mark.parametrize("gatename", ["CRX", "CRY", "CRZ"]) +def test_controlled_rotations_to_native(gatename, natives): + gate = getattr(gates, gatename)(0, 1, 0.3) + assert_matrices_allclose(gate, natives) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +def test_cu1_to_native(natives): + gate = gates.CU1(0, 1, theta=0.4) + assert_matrices_allclose(gate, natives) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +def test_cu2_to_native(natives): + gate = gates.CU2(0, 1, phi=0.2, lam=0.3) + assert_matrices_allclose(gate, natives) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +def test_cu3_to_native(natives): + gate = gates.CU3(0, 1, theta=0.2, phi=0.3, lam=0.4) + assert_matrices_allclose(gate, natives) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +def test_fSim_to_native(natives): + gate = gates.fSim(0, 1, theta=0.3, phi=0.1) + assert_matrices_allclose(gate, natives) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +def test_GeneralizedfSim_to_native(natives): + from .test_transpilers_unitary_decompositions import random_unitary + + unitary = random_unitary(1) + gate = gates.GeneralizedfSim(0, 1, unitary, phi=0.1) + assert_matrices_allclose(gate, natives) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +@pytest.mark.parametrize("gatename", ["RXX", "RZZ", "RYY"]) +def test_rnn_to_native(gatename, natives): + gate = getattr(gates, gatename)(0, 1, theta=0.1) + assert_matrices_allclose(gate, natives) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +def test_TOFFOLI_to_native(natives): + gate = gates.TOFFOLI(0, 1, 2) + assert_matrices_allclose(gate, natives) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +@pytest.mark.parametrize("nqubits", [1, 2]) +def test_unitary_to_native(nqubits, natives): + from .test_transpilers_unitary_decompositions import random_unitary + + u = random_unitary(nqubits) + # transform to SU(2^nqubits) form + u = u / np.sqrt(np.linalg.det(u)) + gate = gates.Unitary(u, *range(nqubits)) + assert_matrices_allclose(gate, natives) + + +def test_count_1q(): + from qibolab.transpilers.unroller import cz_dec + + np.testing.assert_allclose(cz_dec.count_1q(gates.CNOT(0, 1)), 2) + np.testing.assert_allclose(cz_dec.count_1q(gates.CRX(0, 1, 0.1)), 2) + + +def test_count_2q(): + from qibolab.transpilers.unroller import cz_dec + + np.testing.assert_allclose(cz_dec.count_2q(gates.CNOT(0, 1)), 1) + np.testing.assert_allclose(cz_dec.count_2q(gates.CRX(0, 1, 0.1)), 2) + + +def test_assert_decomposition(): + circuit = Circuit(2) + circuit.add(gates.CZ(0, 1)) + circuit.add(gates.Z(0)) + circuit.add(gates.M(1)) + assert_decomposition(circuit, two_qubit_natives=NativeType.CZ) + + +def test_assert_decomposition_fail_1q(): + circuit = Circuit(1) + circuit.add(gates.X(0)) + with pytest.raises(DecompositionError): + assert_decomposition(circuit, two_qubit_natives=NativeType.CZ) + + +@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, two_qubit_natives=NativeType.CZ) + + +def test_assert_decomposition_fail_3q(): + circuit = Circuit(3) + circuit.add(gates.TOFFOLI(0, 1, 2)) + with pytest.raises(DecompositionError): + assert_decomposition(circuit, two_qubit_natives=NativeType.CZ) + + +def test_no_translate_single_qubit(): + unroller = NativeGates( + two_qubit_natives=NativeType.CZ, translate_single_qubit=False + ) + circuit = Circuit(2) + circuit.add(gates.X(0)) + circuit.add(gates.CNOT(0, 1)) + translated_circuit = unroller(circuit) + assert isinstance(translated_circuit.queue[0], gates.X) and isinstance( + translated_circuit.queue[2], gates.CZ + ) From 5a98977af2e9737ce0fb6220b4b8b311f2da26df Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 30 Oct 2023 13:59:00 +0400 Subject: [PATCH 04/55] add more-itertools dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 14bbab1fc7..9ffa182632 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ joblib = "^1.2.0" matplotlib = "^3.7.0" psutil = "^5.9.4" tabulate = "^0.9.0" +more-itertools = "10.1.0" [tool.poetry.group.docs] optional = true From ac7304bace3a3e49b524bc83182520f5f1220137 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 30 Oct 2023 14:07:31 +0400 Subject: [PATCH 05/55] more-itertool dependency --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9ffa182632..6966741742 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ joblib = "^1.2.0" matplotlib = "^3.7.0" psutil = "^5.9.4" tabulate = "^0.9.0" -more-itertools = "10.1.0" +more-itertools = ">=9.1.0" [tool.poetry.group.docs] optional = true From bb29e9b56539a867304e241a344e3ff97df492f7 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 30 Oct 2023 14:17:44 +0400 Subject: [PATCH 06/55] update poetry.lock --- poetry.lock | 416 ++++++++++++++++++++++++++++++++++++++++--------- pyproject.toml | 2 +- 2 files changed, 342 insertions(+), 76 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1fcb3736a4..2546fba8c7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. [[package]] name = "absl-py" version = "2.0.0" description = "Abseil Python Common Libraries, see https://github.com/abseil/abseil-py." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -15,6 +16,7 @@ files = [ name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -26,6 +28,7 @@ files = [ name = "appnope" version = "0.1.3" description = "Disable App Nap on macOS >= 10.9" +category = "dev" optional = false python-versions = "*" files = [ @@ -37,6 +40,7 @@ files = [ name = "astroid" version = "2.15.8" description = "An abstract syntax tree for Python with inference support." +category = "dev" optional = false python-versions = ">=3.7.2" files = [ @@ -56,6 +60,7 @@ wrapt = [ name = "asttokens" version = "2.4.1" description = "Annotate AST trees with source code positions" +category = "dev" optional = false python-versions = "*" files = [ @@ -74,6 +79,7 @@ test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] name = "astunparse" version = "1.6.3" description = "An AST unparser for Python" +category = "dev" optional = false python-versions = "*" files = [ @@ -89,6 +95,7 @@ wheel = ">=0.23.0,<1.0" name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -107,6 +114,7 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "babel" version = "2.13.1" description = "Internationalization utilities" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -124,6 +132,7 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] name = "backcall" version = "0.2.0" description = "Specifications for callback functions passed in to an API" +category = "dev" optional = false python-versions = "*" files = [ @@ -135,6 +144,7 @@ files = [ name = "beautifulsoup4" version = "4.12.2" description = "Screen-scraping library" +category = "dev" optional = false python-versions = ">=3.6.0" files = [ @@ -153,6 +163,7 @@ lxml = ["lxml"] name = "bleach" version = "6.1.0" description = "An easy safelist-based HTML-sanitizing tool." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -171,6 +182,7 @@ css = ["tinycss2 (>=1.1.0,<1.3)"] name = "cachetools" version = "5.3.2" description = "Extensible memoizing collections and decorators" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -182,6 +194,7 @@ files = [ name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -193,6 +206,7 @@ files = [ name = "cffi" version = "1.16.0" description = "Foreign Function Interface for Python calling C code." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -257,6 +271,7 @@ pycparser = "*" name = "charset-normalizer" version = "3.3.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -356,6 +371,7 @@ files = [ name = "cirq" version = "1.1.0" description = "A framework for creating, editing, and invoking Noisy Intermediate Scale Quantum (NISQ) circuits." +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -378,6 +394,7 @@ dev-env = ["asv", "black (==22.3.0)", "codeowners", "coverage (<=6.2)", "dill (= name = "cirq-aqt" version = "1.1.0" description = "A Cirq package to simulate and connect to Alpine Quantum Technologies quantum computers" +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -392,6 +409,7 @@ requests = ">=2.18,<3.0" name = "cirq-core" version = "1.1.0" description = "A framework for creating, editing, and invoking Noisy Intermediate Scale Quantum (NISQ) circuits." +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -417,6 +435,7 @@ contrib = ["autoray", "numba (>=0.53.0)", "opt-einsum", "ply (>=3.6)", "pylatex name = "cirq-google" version = "1.1.0" description = "The Cirq module that provides tools and access to the Google Quantum Computing Service" +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -433,6 +452,7 @@ protobuf = ">=3.15.0,<4" name = "cirq-ionq" version = "1.1.0" description = "A Cirq package to simulate and connect to IonQ quantum computers" +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -447,6 +467,7 @@ requests = ">=2.18,<3.0" name = "cirq-pasqal" version = "1.1.0" description = "A Cirq package to simulate and connect to Pasqal quantum computers" +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -461,6 +482,7 @@ requests = ">=2.18,<3.0" name = "cirq-rigetti" version = "1.1.0" description = "A Cirq package to simulate and connect to Rigetti quantum computers and Quil QVM" +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -475,6 +497,7 @@ pyquil = ">=3.2.0" name = "cirq-web" version = "1.1.0" description = "Web-based 3D visualization tools for Cirq." +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -488,6 +511,7 @@ cirq-core = "1.1.0" name = "clarabel" version = "0.6.0" description = "Clarabel Conic Interior Point Solver for Rust / Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -509,6 +533,7 @@ scipy = "*" name = "cma" version = "3.3.0" description = "CMA-ES, Covariance Matrix Adaptation Evolution Strategy for non-linear numerical optimization in Python" +category = "main" optional = false python-versions = "*" files = [ @@ -527,6 +552,7 @@ plotting = ["matplotlib"] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -538,6 +564,7 @@ files = [ name = "commonmark" version = "0.9.1" description = "Python parser for the CommonMark Markdown spec" +category = "dev" optional = false python-versions = "*" files = [ @@ -552,6 +579,7 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] name = "contourpy" version = "1.1.1" description = "Python library for calculating contours of 2D quadrilateral grids" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -623,6 +651,7 @@ test-no-images = ["pytest", "pytest-cov", "wurlitzer"] name = "coverage" version = "7.3.2" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -690,6 +719,7 @@ toml = ["tomli"] name = "cupy-cuda11x" version = "12.2.0" description = "CuPy: NumPy & SciPy for GPU" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -720,6 +750,7 @@ test = ["hypothesis (>=6.37.2,<6.55.0)", "pytest (>=7.2)"] name = "cupy-cuda12x" version = "12.2.0" description = "CuPy: NumPy & SciPy for GPU" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -750,6 +781,7 @@ test = ["hypothesis (>=6.37.2,<6.55.0)", "pytest (>=7.2)"] name = "cuquantum-python-cu11" version = "23.3.0" description = "NVIDIA cuQuantum Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -772,6 +804,7 @@ numpy = "*" name = "cuquantum-python-cu12" version = "23.3.0" description = "NVIDIA cuQuantum Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -794,6 +827,7 @@ numpy = "*" name = "custatevec-cu11" version = "1.4.1" description = "cuStateVec - a component of NVIDIA cuQuantum SDK" +category = "dev" optional = false python-versions = "*" files = [ @@ -805,6 +839,7 @@ files = [ name = "custatevec-cu12" version = "1.4.1" description = "cuStateVec - a component of NVIDIA cuQuantum SDK" +category = "dev" optional = false python-versions = "*" files = [ @@ -816,6 +851,7 @@ files = [ name = "cutensor-cu11" version = "1.7.0" description = "NVIDIA cuTENSOR" +category = "dev" optional = false python-versions = "*" files = [ @@ -827,6 +863,7 @@ files = [ name = "cutensor-cu12" version = "1.7.0" description = "NVIDIA cuTENSOR" +category = "dev" optional = false python-versions = "*" files = [ @@ -838,6 +875,7 @@ files = [ name = "cutensornet-cu11" version = "2.2.1" description = "cuTensorNet - a component of NVIDIA cuQuantum SDK" +category = "dev" optional = false python-versions = "*" files = [ @@ -852,6 +890,7 @@ cutensor-cu11 = ">=1.6.1,<2" name = "cutensornet-cu12" version = "2.2.1" description = "cuTensorNet - a component of NVIDIA cuQuantum SDK" +category = "dev" optional = false python-versions = "*" files = [ @@ -866,6 +905,7 @@ cutensor-cu12 = ">=1.6.1,<2" name = "cvxpy" version = "1.4.1" description = "A domain-specific language for modeling convex optimization problems in Python." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -924,6 +964,7 @@ xpress = ["xpress"] name = "cycler" version = "0.12.1" description = "Composable style cycles" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -939,6 +980,7 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] name = "decorator" version = "5.1.1" description = "Decorators for Humans" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -950,6 +992,7 @@ files = [ name = "defusedxml" version = "0.7.1" description = "XML bomb protection for Python stdlib modules" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -961,6 +1004,7 @@ files = [ name = "deprecated" version = "1.2.14" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -978,6 +1022,7 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] name = "dill" version = "0.3.7" description = "serialize all of Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -992,6 +1037,7 @@ graph = ["objgraph (>=1.7.2)"] name = "docutils" version = "0.19" description = "Docutils -- Python Documentation Utilities" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1003,6 +1049,7 @@ files = [ name = "duet" version = "0.2.8" description = "A simple future-based async library for python." +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -1011,12 +1058,13 @@ files = [ ] [package.extras] -dev-env = ["black (==22.3.0)", "isort (==5.7.*)", "mypy (==0.931.*)", "pylint (==2.10.*)", "pytest (==6.2.*)", "twine (==3.3.*)"] +dev-env = ["black (==22.3.0)", "isort (>=5.7.0,<5.8.0)", "mypy (>=0.931.0,<0.932.0)", "pylint (>=2.10.0,<2.11.0)", "pytest (>=6.2.0,<6.3.0)", "twine (>=3.3.0,<3.4.0)"] [[package]] name = "ecos" version = "2.0.12" description = "This is the Python package for ECOS: Embedded Cone Solver. See Github page for more information." +category = "dev" optional = false python-versions = "*" files = [ @@ -1046,6 +1094,7 @@ scipy = ">=0.9" name = "exceptiongroup" version = "1.1.3" description = "Backport of PEP 654 (exception groups)" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1058,13 +1107,14 @@ test = ["pytest (>=6)"] [[package]] name = "executing" -version = "2.0.0" +version = "2.0.1" description = "Get the currently executing AST node of a frame, and other information" +category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.5" files = [ - {file = "executing-2.0.0-py2.py3-none-any.whl", hash = "sha256:06df6183df67389625f4e763921c6cf978944721abf3e714000200aab95b0657"}, - {file = "executing-2.0.0.tar.gz", hash = "sha256:0ff053696fdeef426cda5bd18eacd94f82c91f49823a2e9090124212ceea9b08"}, + {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, + {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, ] [package.extras] @@ -1074,6 +1124,7 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth name = "fastjsonschema" version = "2.18.1" description = "Fastest Python implementation of JSON schema" +category = "dev" optional = false python-versions = "*" files = [ @@ -1088,6 +1139,7 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc name = "fastrlock" version = "0.8.2" description = "Fast, re-entrant optimistic lock implemented in Cython" +category = "dev" optional = false python-versions = "*" files = [ @@ -1172,6 +1224,7 @@ files = [ name = "flatbuffers" version = "23.5.26" description = "The FlatBuffers serialization format for Python" +category = "dev" optional = false python-versions = "*" files = [ @@ -1183,6 +1236,7 @@ files = [ name = "fonttools" version = "4.43.1" description = "Tools to manipulate font files" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1248,6 +1302,7 @@ woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] name = "furo" version = "2022.12.7" description = "A clean customisable Sphinx documentation theme." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1265,6 +1320,7 @@ sphinx-basic-ng = "*" name = "gast" version = "0.4.0" description = "Python AST that abstracts the underlying Python version" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1276,6 +1332,7 @@ files = [ name = "google-api-core" version = "1.34.0" description = "Google API client core library" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1300,6 +1357,7 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] name = "google-auth" version = "2.23.3" description = "Google Authentication Library" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1323,6 +1381,7 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"] name = "google-auth-oauthlib" version = "1.0.0" description = "Google Authentication Library" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1341,6 +1400,7 @@ tool = ["click (>=6.0.0)"] name = "google-pasta" version = "0.2.0" description = "pasta is an AST-based Python refactoring library" +category = "dev" optional = false python-versions = "*" files = [ @@ -1356,6 +1416,7 @@ six = "*" name = "googleapis-common-protos" version = "1.61.0" description = "Common protobufs used in Google APIs" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1373,6 +1434,7 @@ grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] name = "grpcio" version = "1.59.0" description = "HTTP/2-based RPC framework" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1439,6 +1501,7 @@ protobuf = ["grpcio-tools (>=1.59.0)"] name = "grpcio-status" version = "1.48.2" description = "Status proto mapping for gRPC" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1455,6 +1518,7 @@ protobuf = ">=3.12.0" name = "h5py" version = "3.10.0" description = "Read and write HDF5 files from Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1492,6 +1556,7 @@ numpy = ">=1.17.3" name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1503,6 +1568,7 @@ files = [ name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1514,6 +1580,7 @@ files = [ name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1533,6 +1600,7 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "importlib-resources" version = "6.1.0" description = "Read resources from Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1551,6 +1619,7 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1562,6 +1631,7 @@ files = [ name = "ipython" version = "8.12.3" description = "IPython: Productive Interactive Computing" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1601,6 +1671,7 @@ test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pa name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." +category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -1618,6 +1689,7 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "jax" version = "0.4.13" description = "Differentiate, compile, and transform Numpy code." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1648,6 +1720,7 @@ tpu = ["jaxlib (==0.4.13)", "libtpu-nightly (==0.1.dev20230622)"] name = "jedi" version = "0.19.1" description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1667,6 +1740,7 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1684,6 +1758,7 @@ i18n = ["Babel (>=2.7)"] name = "joblib" version = "1.3.2" description = "Lightweight pipelining with Python functions" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1695,6 +1770,7 @@ files = [ name = "jsonschema" version = "4.19.1" description = "An implementation of JSON Schema validation for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1718,6 +1794,7 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- name = "jsonschema-specifications" version = "2023.7.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1733,6 +1810,7 @@ referencing = ">=0.28.0" name = "jupyter-client" version = "8.5.0" description = "Jupyter protocol implementation and client libraries" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1742,7 +1820,7 @@ files = [ [package.dependencies] importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" python-dateutil = ">=2.8.2" pyzmq = ">=23.0" tornado = ">=6.2" @@ -1756,6 +1834,7 @@ test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pyt name = "jupyter-core" version = "5.4.0" description = "Jupyter core package. A base package on which Jupyter projects rely." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1776,6 +1855,7 @@ test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] name = "jupyterlab-pygments" version = "0.2.2" description = "Pygments theme using JupyterLab CSS variables" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1787,6 +1867,7 @@ files = [ name = "keras" version = "2.12.0" description = "Deep learning for humans." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1797,6 +1878,7 @@ files = [ name = "kiwisolver" version = "1.4.5" description = "A fast implementation of the Cassowary constraint solver" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1910,6 +1992,7 @@ files = [ name = "lark" version = "0.11.3" description = "a modern parsing library" +category = "dev" optional = false python-versions = "*" files = [ @@ -1925,6 +2008,7 @@ regex = ["regex"] name = "latexcodec" version = "2.0.1" description = "A lexer and codec to work with LaTeX code in Python." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1939,6 +2023,7 @@ six = ">=1.4.1" name = "lazy-object-proxy" version = "1.9.0" description = "A fast and thorough lazy object proxy." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1984,6 +2069,7 @@ files = [ name = "libclang" version = "16.0.6" description = "Clang Python Bindings, mirrored from the official LLVM repo: https://github.com/llvm/llvm-project/tree/main/clang/bindings/python, to make the installation process easier." +category = "dev" optional = false python-versions = "*" files = [ @@ -2004,6 +2090,7 @@ files = [ name = "llvmlite" version = "0.41.1" description = "lightweight wrapper around basic LLVM functionality" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2037,6 +2124,7 @@ files = [ name = "markdown" version = "3.5" description = "Python implementation of John Gruber's Markdown." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2055,6 +2143,7 @@ testing = ["coverage", "pyyaml"] name = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2078,6 +2167,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -2114,6 +2213,7 @@ files = [ name = "matplotlib" version = "3.7.3" description = "Python plotting package" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2183,6 +2283,7 @@ setuptools_scm = ">=7" name = "matplotlib-inline" version = "0.1.6" description = "Inline Matplotlib backend for Jupyter" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2197,6 +2298,7 @@ traitlets = "*" name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2208,6 +2310,7 @@ files = [ name = "mistune" version = "3.0.2" description = "A sane and fast Markdown parser with useful plugins and renderers" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2219,6 +2322,7 @@ files = [ name = "ml-dtypes" version = "0.2.0" description = "" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2243,18 +2347,31 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.21.2", markers = "python_version > \"3.9\" and python_version <= \"3.10\""}, {version = ">1.20", markers = "python_version <= \"3.9\""}, + {version = ">=1.21.2", markers = "python_version > \"3.9\""}, {version = ">=1.23.3", markers = "python_version > \"3.10\""}, ] [package.extras] dev = ["absl-py", "pyink", "pylint (>=2.6.0)", "pytest", "pytest-xdist"] +[[package]] +name = "more-itertools" +version = "9.1.0" +description = "More routines for operating on iterables, beyond itertools" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "more-itertools-9.1.0.tar.gz", hash = "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d"}, + {file = "more_itertools-9.1.0-py3-none-any.whl", hash = "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3"}, +] + [[package]] name = "mpmath" version = "1.3.0" description = "Python library for arbitrary-precision floating-point arithmetic" +category = "main" optional = false python-versions = "*" files = [ @@ -2272,6 +2389,7 @@ tests = ["pytest (>=4.6)"] name = "msgpack" version = "1.0.7" description = "MessagePack serializer" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2337,6 +2455,7 @@ files = [ name = "nbclient" version = "0.8.0" description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." +category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -2346,7 +2465,7 @@ files = [ [package.dependencies] jupyter-client = ">=6.1.12" -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" nbformat = ">=5.1" traitlets = ">=5.4" @@ -2359,6 +2478,7 @@ test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>= name = "nbconvert" version = "7.9.2" description = "Converting Jupyter Notebooks" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2397,6 +2517,7 @@ webpdf = ["playwright"] name = "nbformat" version = "5.9.2" description = "The Jupyter Notebook format" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2418,6 +2539,7 @@ test = ["pep440", "pre-commit", "pytest", "testpath"] name = "nbsphinx" version = "0.8.12" description = "Jupyter Notebook Tools for Sphinx" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2437,6 +2559,7 @@ traitlets = ">=5" name = "networkx" version = "2.8.8" description = "Python package for creating and manipulating graphs and networks" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2455,6 +2578,7 @@ test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] name = "numba" version = "0.58.1" description = "compiling Python code using LLVM" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2483,13 +2607,14 @@ files = [ [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.9\""} -llvmlite = "==0.41.*" +llvmlite = ">=0.41.0dev0,<0.42" numpy = ">=1.22,<1.27" [[package]] name = "numpy" version = "1.23.5" description = "NumPy is the fundamental package for array computing with Python." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2527,6 +2652,7 @@ files = [ name = "oauthlib" version = "3.2.2" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2543,6 +2669,7 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] name = "opt-einsum" version = "3.3.0" description = "Optimizing numpys einsum function" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2561,6 +2688,7 @@ tests = ["pytest", "pytest-cov", "pytest-pep8"] name = "osqp" version = "0.6.3" description = "OSQP: The Operator Splitting QP Solver" +category = "dev" optional = false python-versions = "*" files = [ @@ -2600,6 +2728,7 @@ scipy = ">=0.13.2" name = "packaging" version = "23.2" description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2611,6 +2740,7 @@ files = [ name = "pandas" version = "2.0.3" description = "Powerful data structures for data analysis, time series, and statistics" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2644,7 +2774,7 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.20.3", markers = "python_version < \"3.10\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, + {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, ] python-dateutil = ">=2.8.2" @@ -2678,6 +2808,7 @@ xml = ["lxml (>=4.6.3)"] name = "pandocfilters" version = "1.5.0" description = "Utilities for writing pandoc filters in python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2689,6 +2820,7 @@ files = [ name = "parso" version = "0.8.3" description = "A Python Parser" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2704,6 +2836,7 @@ testing = ["docopt", "pytest (<6.0.0)"] name = "pexpect" version = "4.8.0" description = "Pexpect allows easy control of interactive console applications." +category = "dev" optional = false python-versions = "*" files = [ @@ -2718,6 +2851,7 @@ ptyprocess = ">=0.5" name = "pickleshare" version = "0.7.5" description = "Tiny 'shelve'-like database with concurrency support" +category = "dev" optional = false python-versions = "*" files = [ @@ -2729,6 +2863,7 @@ files = [ name = "pillow" version = "10.1.0" description = "Python Imaging Library (Fork)" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2796,6 +2931,7 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa name = "pkgutil-resolve-name" version = "1.3.10" description = "Resolve a name to an object." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2807,6 +2943,7 @@ files = [ name = "platformdirs" version = "3.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2822,6 +2959,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2837,6 +2975,7 @@ testing = ["pytest", "pytest-benchmark"] name = "ply" version = "3.11" description = "Python Lex & Yacc" +category = "dev" optional = false python-versions = "*" files = [ @@ -2848,6 +2987,7 @@ files = [ name = "prompt-toolkit" version = "3.0.39" description = "Library for building powerful interactive command lines in Python" +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -2862,6 +3002,7 @@ wcwidth = "*" name = "proto-plus" version = "1.22.3" description = "Beautiful, Pythonic protocol buffers." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2879,6 +3020,7 @@ testing = ["google-api-core[grpc] (>=1.31.5)"] name = "protobuf" version = "3.20.3" description = "Protocol Buffers" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2910,6 +3052,7 @@ files = [ name = "psutil" version = "5.9.6" description = "Cross-platform lib for process and system monitoring in Python." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -2938,6 +3081,7 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] name = "ptyprocess" version = "0.7.0" description = "Run a subprocess in a pseudo terminal" +category = "dev" optional = false python-versions = "*" files = [ @@ -2949,6 +3093,7 @@ files = [ name = "pure-eval" version = "0.2.2" description = "Safely evaluate AST nodes without side effects" +category = "dev" optional = false python-versions = "*" files = [ @@ -2963,6 +3108,7 @@ tests = ["pytest"] name = "pyasn1" version = "0.5.0" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -2974,6 +3120,7 @@ files = [ name = "pyasn1-modules" version = "0.3.0" description = "A collection of ASN.1-based protocols modules" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -2988,6 +3135,7 @@ pyasn1 = ">=0.4.6,<0.6.0" name = "pybind11" version = "2.11.1" description = "Seamless operability between C++11 and Python" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3002,6 +3150,7 @@ global = ["pybind11-global (==2.11.1)"] name = "pybtex" version = "0.24.0" description = "A BibTeX-compatible bibliography processor in Python" +category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" files = [ @@ -3021,6 +3170,7 @@ test = ["pytest"] name = "pybtex-docutils" version = "1.0.3" description = "A docutils backend for pybtex." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3036,6 +3186,7 @@ pybtex = ">=0.16" name = "pycparser" version = "2.21" description = "C parser in Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3047,6 +3198,7 @@ files = [ name = "pydantic" version = "1.10.13" description = "Data validation and settings management using python type hints" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3099,6 +3251,7 @@ email = ["email-validator (>=1.0.3)"] name = "pygments" version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3113,6 +3266,7 @@ plugins = ["importlib-metadata"] name = "pylint" version = "2.17.7" description = "python code static checker" +category = "dev" optional = false python-versions = ">=3.7.2" files = [ @@ -3142,6 +3296,7 @@ testutils = ["gitpython (>3)"] name = "pyparsing" version = "3.1.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" optional = false python-versions = ">=3.6.8" files = [ @@ -3156,6 +3311,7 @@ diagrams = ["jinja2", "railroad-diagrams"] name = "pyquil" version = "4.0.3" description = "A Python library for creating Quantum Instruction Language (Quil) programs." +category = "dev" optional = false python-versions = ">=3.8,<4.0" files = [ @@ -3187,6 +3343,7 @@ latex = ["ipython (>=7.21.0,<8.0.0)"] name = "pytest" version = "7.4.3" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3209,6 +3366,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3227,6 +3385,7 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -3239,73 +3398,75 @@ six = ">=1.5" [[package]] name = "python-rapidjson" -version = "1.12" +version = "1.13" description = "Python wrapper around rapidjson" +category = "dev" optional = false python-versions = ">=3.6" files = [ - {file = "python-rapidjson-1.12.tar.gz", hash = "sha256:2d10530c515135eeb4b197564a0b8efea477c3b52b67d057a833f82e38e2dfd2"}, - {file = "python_rapidjson-1.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7fc9b9c6d54ee9bebd8a566730e59662cc5b55c02959a7c9f97ab716af8d8287"}, - {file = "python_rapidjson-1.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6efc5dba0f3dd0afb3d81221dadb3c5911199ea9af8b387b8c3667c53c77f6ad"}, - {file = "python_rapidjson-1.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:160bf777de26a9d61b8778fc4d3e1dafa6e0ff6778ea8a2e8536cc2c7e51c29a"}, - {file = "python_rapidjson-1.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bad56304d76123cd39e1a66b43b304ff41c4328a48870dda48ae9602eb0618"}, - {file = "python_rapidjson-1.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:065bf14e01cb66a2ffe79aac1d7a54e71a06ce12d9beb4f87257fea12ef4375d"}, - {file = "python_rapidjson-1.12-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a1f790ed242fa2601ca72904187e20f56eebdda13805432dd84de06b37ebc91c"}, - {file = "python_rapidjson-1.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4eb6774541cbc1030e129965f5c7144bc9c2e6c7530db5d3c5d25b59fdd467f0"}, - {file = "python_rapidjson-1.12-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:cd3ae2451df96bca2a8f0af6932a7f1a8118c7560930d7c1e41d10fa4f86df61"}, - {file = "python_rapidjson-1.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7fa25b60f2db28e0b3f3d0d394133404107f47eafd2d03f31d3f25a0832133ac"}, - {file = "python_rapidjson-1.12-cp310-cp310-win32.whl", hash = "sha256:c6175d274d90a06f9706a888368881db3ff18c602baaf30a79e7e75dee0846ae"}, - {file = "python_rapidjson-1.12-cp310-cp310-win_amd64.whl", hash = "sha256:31f3516b5cef7a9e0f7eedaef7af89ffd2683c8b39048b71d7e4ce303136a371"}, - {file = "python_rapidjson-1.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:abe20042832b193718a9c0f87c71b14920b8f1265fdc65643c1e011ceb9b536d"}, - {file = "python_rapidjson-1.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c4415dd8082b945321a8fc8f05612e90e37225c49dcc980619d55fb8d81789b"}, - {file = "python_rapidjson-1.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f80cdaf30b63d8aef3a2297c2fbf43ff3d2b58cd7b35513f4adfe06210d5279"}, - {file = "python_rapidjson-1.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ef3fb9543a5e2eaddd805a200689db6352695b59ce8acf93304e561690c10bf"}, - {file = "python_rapidjson-1.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4491383002176ff205dfeb0c90250721594b504fce7aaf78a94f512d8ac4a03f"}, - {file = "python_rapidjson-1.12-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cb94eb36cf152bcee455a9f233726ca1038bdc88579de17f3b2f2d111e122b44"}, - {file = "python_rapidjson-1.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cbf459b1ab1f69f8d234bf35ecd22799f08807de23df1c51b163b8d38a1616b0"}, - {file = "python_rapidjson-1.12-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d524d03b004ae1a54c413ddf74591dacc9facdb96d71a348ddefaa6ba0098a7c"}, - {file = "python_rapidjson-1.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d6597f78930f4bbbbce69d0835347ef4c2bc809fa149b4dca57879228f55a03b"}, - {file = "python_rapidjson-1.12-cp311-cp311-win32.whl", hash = "sha256:c2f7456715f48ac0c07aa771848cc0f9dc7fcbe9142a4614a44496ce7868c941"}, - {file = "python_rapidjson-1.12-cp311-cp311-win_amd64.whl", hash = "sha256:712644728a9d8743e4881369623723e18291fe79c684a493b19f50b91220ab2f"}, - {file = "python_rapidjson-1.12-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbb7ee6a9f04508b695a3e8b067aca5f780c9bc9969808bbe7511c2bc3d0d5e"}, - {file = "python_rapidjson-1.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdcf4dd9986d7229ff463771a248419ba168ab733acefe2f78d353cafcca564"}, - {file = "python_rapidjson-1.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a05ed1efd85d09f64c994fbcaeae2816e399fe6fc20df952566b82f3859a0b79"}, - {file = "python_rapidjson-1.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a65aed90fca1e21a9862ff0024b006ac5056d3bc54a7270c1f1de3208842bf24"}, - {file = "python_rapidjson-1.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f8633b4951778b981e25afdec62234ee6e733ecd7c0af1b9f0f0ccc90bf4a39"}, - {file = "python_rapidjson-1.12-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fd3ce6194060f4d19cd623acdcdf6d4f0048417e0e59191a7846a0da088de5bd"}, - {file = "python_rapidjson-1.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:610400ed807f404c8eea6de926e8f597ebcc2bc61d1741146047fc5eba933611"}, - {file = "python_rapidjson-1.12-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7416d938bb9860e7e06c2d5194d181b8f330636f28e5a9a77fa2794fa614c966"}, - {file = "python_rapidjson-1.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5effe98380a1fdd24c3cb622d8b5da448db2d8dc45ed4c5574fad1bcb3a7208c"}, - {file = "python_rapidjson-1.12-cp312-cp312-win32.whl", hash = "sha256:8cba24ddd21342db7abdad383028a4b7f02d5a926951e0686ca19f34f5df3204"}, - {file = "python_rapidjson-1.12-cp312-cp312-win_amd64.whl", hash = "sha256:442c0bb847965c73c347b4a10731b8762463ec92ddc8fd98a9c9ed337bd3a435"}, - {file = "python_rapidjson-1.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ce24c4c503c0646f693bca47509e6ab69fabad7b10852ec4cf8aeb470d50c892"}, - {file = "python_rapidjson-1.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:622f10964559d7f4ec27c5ca6f91c0b08d9d39d19f4d94ce4f9711c8ed14b38a"}, - {file = "python_rapidjson-1.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0804c1518696ea3ba251167f97e2a82ecc9ae80141dde626b1fd084d3bd78c1e"}, - {file = "python_rapidjson-1.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23bba42123e666f9043505801653cf27e3bfc5656c26104fc946ec0336f79963"}, - {file = "python_rapidjson-1.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5711de9ac945936e688e29ab0892d4e869e8e632c0f607334d5801226826135e"}, - {file = "python_rapidjson-1.12-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae16eed4840f04953580ce5b8b323cf3d856e48add512a9597faba1c2d68577f"}, - {file = "python_rapidjson-1.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a0add3c454bf8af81a2d3cc4deba2281fc0df0ea3cac0c12886c8004095460dc"}, - {file = "python_rapidjson-1.12-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f952eb5d83b77ae78108c495ee47bc1668aa81596f9478e104fd769ffccb1c31"}, - {file = "python_rapidjson-1.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5e139fa50b795295a50f839ad888fd110ab3ef1e067074dba8c1107c20f276af"}, - {file = "python_rapidjson-1.12-cp38-cp38-win32.whl", hash = "sha256:1b75774ef0ce5b20b76939b4a25aff06b7f545d83cd710d5db2d522a5f154e82"}, - {file = "python_rapidjson-1.12-cp38-cp38-win_amd64.whl", hash = "sha256:08fd8f163617c0155843d5af65e3b6bec35d80dbdc86774435877384ded278a6"}, - {file = "python_rapidjson-1.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:28f59b341221a65dcd36bf0cbb21443a7f5d64146f24b05dd89b41d34963c726"}, - {file = "python_rapidjson-1.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b08f21ab15a0a22b7b556cda607fc268cb29ac029ec5c773a12bea424f8abb20"}, - {file = "python_rapidjson-1.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:540262286f1d87c73fa4930976f5b4de82d1244694936e87b12fce9e9a205569"}, - {file = "python_rapidjson-1.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca3f849e0c1e990e527d697bf5b14a6349bdb0a79b82214bf3eea415461003f7"}, - {file = "python_rapidjson-1.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9a6acb2745c32b353d49c5e06d1d6229820e693a07b6bbe79d3f31393e44495"}, - {file = "python_rapidjson-1.12-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fda536d92ec85e6624f7d9c60f18f2cbd95a3508fcc8178397c918df4afd9313"}, - {file = "python_rapidjson-1.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:199f7183e19ff1b0d9a408fd776d42492cf913a5f11b847cc3f1751742abca5a"}, - {file = "python_rapidjson-1.12-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e759516757d9ad15c6e0373084686a484e9ec3bac2087cfef5b5e15c5473bfef"}, - {file = "python_rapidjson-1.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b09429ac1bd78bca0230fcb6aea2da3bdb9473b761f3e755a52a346eb184ffc"}, - {file = "python_rapidjson-1.12-cp39-cp39-win32.whl", hash = "sha256:3bb340169bb93358815a7db622d26ce6d3094b89eebae4b3f61c40c8e1759c60"}, - {file = "python_rapidjson-1.12-cp39-cp39-win_amd64.whl", hash = "sha256:e222bab97c32798a005894281ed5c8dbbc2fec53fa70d64e8b8d6c9979daa921"}, + {file = "python-rapidjson-1.13.tar.gz", hash = "sha256:ffe9d382be84ddcf197c188d2f49baa8b47270cec95a1dcb62496e2af3eb71d4"}, + {file = "python_rapidjson-1.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12b0dad7a605d6482fecae18046d91ec772cef1db5d506256b5f399bf0de9b79"}, + {file = "python_rapidjson-1.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87ffa883e20bbb320695b68393b120d916e4bc9125da3ece1f5c32c65f3d2022"}, + {file = "python_rapidjson-1.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f6ea7c0d41a5daf1fbc91f749084bf41afcd48d9883a1fa00ec806fc04f2af2"}, + {file = "python_rapidjson-1.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:016c0143832f9679eaef35683c606086cea937a98c264cf830adfd0887cb1f68"}, + {file = "python_rapidjson-1.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e065e68dcada7df47c74fa974a5293601c97e8d38c1b4acea1700eb1f7ce1b18"}, + {file = "python_rapidjson-1.13-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7a700730b5109207799025aeade538ab830a7bbefc8612cfe565e660bdae706d"}, + {file = "python_rapidjson-1.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ee8bf49beccaa5acd458bc83e45d49892c906830580ecc17a744f15cfe752366"}, + {file = "python_rapidjson-1.13-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a6d910663a1f6c5c1658b176824e46dcf30370c3939a21575c78906e06e06ab3"}, + {file = "python_rapidjson-1.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ede37e4640cb9aaeb812963d2098f8bbff9a86b95a5638e9222bb619ba3a68c0"}, + {file = "python_rapidjson-1.13-cp310-cp310-win32.whl", hash = "sha256:2c86448740a4085197e8e3708c68bd21d3dfaa6d1b9821824a59dc22e90c1fc7"}, + {file = "python_rapidjson-1.13-cp310-cp310-win_amd64.whl", hash = "sha256:4873a3e2ee676f26c4cfdd0026f2a09220868e3df79d6981fab87f4bb5689588"}, + {file = "python_rapidjson-1.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ceca087be345f383999faf388298300f9a40a4e9d5cac142c0e1ce3f9ee3249"}, + {file = "python_rapidjson-1.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa60bf1bc77e8db0d4edafc92130dc70cb1b06c3ef47abaa83bef43198212b9b"}, + {file = "python_rapidjson-1.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dcc302a70a7addcced0901873bce03319b75035ed5bfda144be9b1c8ef4c724e"}, + {file = "python_rapidjson-1.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b951a1bece7ad6224c00634f0e2b52311830b10dead62713d2822979013a0eb"}, + {file = "python_rapidjson-1.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:677f7e3173aff366fb673abca99aa9e477b3607d86ecf83b21e8f5b2144db4c9"}, + {file = "python_rapidjson-1.13-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3d9c071acb3c9c6dc4a4a3cd2c776a09d5605e92610ff462df40803c7135f734"}, + {file = "python_rapidjson-1.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:438bed3cf5c0aebbbbdf5b545fa641051f3c34f5f3d991adbe0d57cf0dba4735"}, + {file = "python_rapidjson-1.13-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:ccdb9cf48a451bf69a862285f45ee69ce96109400c8823d85d385af13a977168"}, + {file = "python_rapidjson-1.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:69d15112ee3ed1ed7e8edc54a2fbbc3c21bca642faf64c0a3951ae8dfdfc53dc"}, + {file = "python_rapidjson-1.13-cp311-cp311-win32.whl", hash = "sha256:10d236ab4c04d98b427282f87a5c600d482ad001653f07d72d94319e043a029e"}, + {file = "python_rapidjson-1.13-cp311-cp311-win_amd64.whl", hash = "sha256:263828991e0335685feb283e8fd576de7520b0c7b61000bdb5d9f55d3b509c51"}, + {file = "python_rapidjson-1.13-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df351d0aa2f2b70a911964233938c1a0907b5df4f92a0ff7fa5724f9b05eadfd"}, + {file = "python_rapidjson-1.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c651217b33886b826b1e1d0506e2e7072f03c8e98b3e23edd5fb52d717a3d7cc"}, + {file = "python_rapidjson-1.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4557aebbdd93fa54e51cb9a66aca29096253803dfcfad9210378c1230e24d1dc"}, + {file = "python_rapidjson-1.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f682fa579de6ef4b0fb8308d3810e54acece8075864f061be380efab1363545"}, + {file = "python_rapidjson-1.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09cb57befa52eb6d229540bc268e9db0336428025773a282140b6d6a8c0b0cfc"}, + {file = "python_rapidjson-1.13-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bffb9953ac4adfddbb186f26a23b668697a1a18c9e6242238f911215fc4c19df"}, + {file = "python_rapidjson-1.13-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:c726c8ee15332b4257e11fc2f2f6d8c3b893abbc4529f7d0fe4db245d5d48958"}, + {file = "python_rapidjson-1.13-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:127c1511f5b6e792257a384b1d5adf9f510a0b9d7a9125e136efcf97c7f12d59"}, + {file = "python_rapidjson-1.13-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:674e924dcafb34568f1e9e86487a3a3cd608408b0656f593ead59f71550a8153"}, + {file = "python_rapidjson-1.13-cp312-cp312-win32.whl", hash = "sha256:d626ac73df1214ee69c75eadc407107715f38f895e52645c529d3cd0a98f06b8"}, + {file = "python_rapidjson-1.13-cp312-cp312-win_amd64.whl", hash = "sha256:896e5a748a9a153f03d47197f8e324948fb7815ae911c4da58f9d4b259541a83"}, + {file = "python_rapidjson-1.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbd3fc5cd5a9c29064c3df8ad24c8f8f8e2b41244fb041c7889ed23b09224898"}, + {file = "python_rapidjson-1.13-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67aa202d5c6d957c8f2d95ee5e381de71215e9e31276763abd07ac0697f1850b"}, + {file = "python_rapidjson-1.13-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bdd559c991bee7c745834ecfd31db7286612cd72bdbe17706b225ad09063341"}, + {file = "python_rapidjson-1.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7fd4d54d9ced3a35a263e34daac36ff190d4927f80bd1d5b180e9114e172227"}, + {file = "python_rapidjson-1.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fca75f4e671eb83b7342c735a605ca85b0c9a3107bdba8f78e9c620b4f020f0c"}, + {file = "python_rapidjson-1.13-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3677a1534f43302736a87299ada097dd96b5644bfcd9997be5e1b15f535acdd7"}, + {file = "python_rapidjson-1.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3fd925c62c8c2a16e185c5e9bc3d3bc43c10c6ac0f8c50f99b6339dfc35d5234"}, + {file = "python_rapidjson-1.13-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:e5f44d35677b9dbb0dec8223cc7a0824c7d1947e2fcc207453ccd2dafc3786a9"}, + {file = "python_rapidjson-1.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c1f56d2546c2f2235595b14535e17037bf2a7956542c3eb65032b6282fdd9bf7"}, + {file = "python_rapidjson-1.13-cp38-cp38-win32.whl", hash = "sha256:93eebbbfd1cffffdfac0f02d11757f4a8017465ea88be24f8fbb0f22bf97f21e"}, + {file = "python_rapidjson-1.13-cp38-cp38-win_amd64.whl", hash = "sha256:7884325958745ec047a15eaa2c5b6b95b94bf22812e1d8c991729de39ae59b48"}, + {file = "python_rapidjson-1.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7789994fa71df0da256f5b8afe3949be293840c31d31de89ced058d5bce08ef"}, + {file = "python_rapidjson-1.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93db30e414770452119ff4bfa5234aebd36476663ea3542685a388b78a98e0e7"}, + {file = "python_rapidjson-1.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29fb34ee79c3e4646845edb7ddf857cdaeb3a30fbcd55b91bf9e79a04b9fde60"}, + {file = "python_rapidjson-1.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ffbe8b4bd475af1571bfc61679685a624740a19441fdd72fb360db2dc96a7a"}, + {file = "python_rapidjson-1.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d66f4196b35ce962bfce185d9860bbe23e5a075b48db7b8318650be3aefa307"}, + {file = "python_rapidjson-1.13-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1b9c8b7c0b99ad0bf5b1493883ce4a8a447ac30c0c6d8f26232c0ed6e8ed578f"}, + {file = "python_rapidjson-1.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf42f367c7950cc7169cfcd78e54d8ed607207e9d078fe13b1f5ffc992910960"}, + {file = "python_rapidjson-1.13-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:74b7ff43aecac7c83e1f3d2cce363c0b01c68923d6dd6874c5607b1eb2f61504"}, + {file = "python_rapidjson-1.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ad430a03e180619b1f1e3d9e2a129bbfa2e7aed3e49bf2ef4db274286e8379a"}, + {file = "python_rapidjson-1.13-cp39-cp39-win32.whl", hash = "sha256:de295c31400a52f2d8b04f6358093fc574eef53e2a49faf7a334cc4adfc43aea"}, + {file = "python_rapidjson-1.13-cp39-cp39-win_amd64.whl", hash = "sha256:599fcf313db14b762204fbc325c5630516a2a1953e32015e37d06d4cd631800e"}, ] [[package]] name = "pytz" version = "2023.3.post1" description = "World timezone definitions, modern and historical" +category = "dev" optional = false python-versions = "*" files = [ @@ -3317,6 +3478,7 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" +category = "dev" optional = false python-versions = "*" files = [ @@ -3340,6 +3502,7 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3348,6 +3511,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -3355,8 +3519,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -3373,6 +3544,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -3380,6 +3552,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -3389,6 +3562,7 @@ files = [ name = "pyzmq" version = "25.1.1" description = "Python bindings for 0MQ" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3494,6 +3668,7 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} name = "qcs-sdk-python" version = "0.12.8" description = "Python interface for the QCS Rust SDK" +category = "dev" optional = false python-versions = "*" files = [ @@ -3523,6 +3698,7 @@ quil = "0.5.7" name = "qdldl" version = "0.1.7.post0" description = "QDLDL, a free LDL factorization routine." +category = "dev" optional = false python-versions = "*" files = [ @@ -3555,6 +3731,7 @@ scipy = ">=0.13.2" name = "qibojit" version = "0.1.1" description = "Simulation tools based on numba and cupy." +category = "dev" optional = false python-versions = ">=3.8.0,<3.12" files = [] @@ -3576,6 +3753,7 @@ resolved_reference = "0519c0e2273563d6439583fdca8d3010acb5dd19" name = "quil" version = "0.5.7" description = "A Python package for building and parsing Quil programs." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3605,6 +3783,7 @@ files = [ name = "recommonmark" version = "0.7.1" description = "A docutils-compatibility bridge to CommonMark, enabling you to write CommonMark inside of Docutils & Sphinx projects." +category = "dev" optional = false python-versions = "*" files = [ @@ -3621,6 +3800,7 @@ sphinx = ">=1.3.1" name = "referencing" version = "0.30.2" description = "JSON Referencing + Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3636,6 +3816,7 @@ rpds-py = ">=0.7.0" name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3657,6 +3838,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-oauthlib" version = "1.3.1" description = "OAuthlib authentication support for Requests." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3675,6 +3857,7 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"] name = "rpcq" version = "3.11.0" description = "The RPC framework and message specification for Rigetti QCS." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3691,6 +3874,7 @@ pyzmq = ">=17" name = "rpds-py" version = "0.10.6" description = "Python bindings to Rust's persistent data structures (rpds)" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3799,6 +3983,7 @@ files = [ name = "rsa" version = "4.9" description = "Pure-Python RSA implementation" +category = "dev" optional = false python-versions = ">=3.6,<4" files = [ @@ -3811,13 +3996,14 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruamel-yaml" -version = "0.18.2" +version = "0.18.3" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +category = "dev" optional = false python-versions = ">=3" files = [ - {file = "ruamel.yaml-0.18.2-py3-none-any.whl", hash = "sha256:92076ac8a83dbf44ca661dbed3c935229c8cbc2f10b05959dd3bd5292d8353d3"}, - {file = "ruamel.yaml-0.18.2.tar.gz", hash = "sha256:9bce33f7a814cea4c29a9c62fe872d2363d6220b767891d956eacea8fa5e6fe8"}, + {file = "ruamel.yaml-0.18.3-py3-none-any.whl", hash = "sha256:b5d119e1f9678cf90b58f64bbd2a4e78af76860ae39fab3e73323e622b462df9"}, + {file = "ruamel.yaml-0.18.3.tar.gz", hash = "sha256:36dbbe90390d977f957436570d2bd540bfd600e6ec5a1ea42bcdb9fc7963d802"}, ] [package.dependencies] @@ -3831,36 +4017,57 @@ jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] name = "ruamel-yaml-clib" version = "0.2.8" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +category = "dev" optional = false python-versions = ">=3.6" files = [ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, @@ -3870,6 +4077,7 @@ files = [ name = "scikit-learn" version = "1.3.2" description = "A set of python modules for machine learning and data mining" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3917,6 +4125,7 @@ tests = ["black (>=23.3.0)", "matplotlib (>=3.1.3)", "mypy (>=1.3)", "numpydoc ( name = "scipy" version = "1.10.1" description = "Fundamental algorithms for scientific computing in Python" +category = "main" optional = false python-versions = "<3.12,>=3.8" files = [ @@ -3955,6 +4164,7 @@ test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeo name = "scs" version = "3.2.3" description = "scs: splitting conic solver" +category = "dev" optional = false python-versions = "*" files = [ @@ -3984,6 +4194,7 @@ scipy = ">=0.13.2" name = "setuptools" version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4000,6 +4211,7 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar name = "setuptools-scm" version = "8.0.4" description = "the blessed package to manage your versions by scm tags" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4022,6 +4234,7 @@ test = ["build", "pytest", "rich", "wheel"] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -4033,6 +4246,7 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" optional = false python-versions = "*" files = [ @@ -4044,6 +4258,7 @@ files = [ name = "sortedcontainers" version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +category = "dev" optional = false python-versions = "*" files = [ @@ -4055,6 +4270,7 @@ files = [ name = "soupsieve" version = "2.5" description = "A modern CSS selector implementation for Beautiful Soup." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4066,6 +4282,7 @@ files = [ name = "sphinx" version = "6.2.1" description = "Python documentation generator" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4101,6 +4318,7 @@ test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] name = "sphinx-basic-ng" version = "1.0.0b2" description = "A modern skeleton for Sphinx themes." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4118,6 +4336,7 @@ docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-ta name = "sphinx-copybutton" version = "0.5.2" description = "Add a copy button to each of your code cells." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4136,6 +4355,7 @@ rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] name = "sphinx-markdown-tables" version = "0.0.17" description = "A Sphinx extension for rendering tables written in markdown" +category = "dev" optional = false python-versions = "*" files = [ @@ -4150,6 +4370,7 @@ markdown = ">=3.4" name = "sphinxcontrib-applehelp" version = "1.0.4" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4165,6 +4386,7 @@ test = ["pytest"] name = "sphinxcontrib-bibtex" version = "2.5.0" description = "Sphinx extension for BibTeX style citations." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -4183,6 +4405,7 @@ Sphinx = ">=2.1" name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -4198,6 +4421,7 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.1" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4213,6 +4437,7 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -4227,6 +4452,7 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -4242,6 +4468,7 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -4257,6 +4484,7 @@ test = ["pytest"] name = "stack-data" version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" +category = "dev" optional = false python-versions = "*" files = [ @@ -4276,6 +4504,7 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] name = "sympy" version = "1.12" description = "Computer algebra system (CAS) in Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4290,6 +4519,7 @@ mpmath = ">=0.19" name = "tabulate" version = "0.9.0" description = "Pretty-print tabular data" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4304,6 +4534,7 @@ widechars = ["wcwidth"] name = "tenacity" version = "8.2.3" description = "Retry code until it succeeds" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4318,6 +4549,7 @@ doc = ["reno", "sphinx", "tornado (>=4.5)"] name = "tensorboard" version = "2.12.3" description = "TensorBoard lets you watch Tensors Flow" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4342,6 +4574,7 @@ wheel = ">=0.26" name = "tensorboard-data-server" version = "0.7.2" description = "Fast data loading for TensorBoard" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4354,6 +4587,7 @@ files = [ name = "tensorflow" version = "2.12.0" description = "TensorFlow is an open source machine learning framework for everyone." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4403,6 +4637,7 @@ wrapt = ">=1.11.0,<1.15" name = "tensorflow-estimator" version = "2.12.0" description = "TensorFlow Estimator." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4413,6 +4648,7 @@ files = [ name = "tensorflow-io-gcs-filesystem" version = "0.34.0" description = "TensorFlow IO" +category = "dev" optional = false python-versions = ">=3.7, <3.12" files = [ @@ -4448,6 +4684,7 @@ tensorflow-rocm = ["tensorflow-rocm (>=2.13.0,<2.14.0)"] name = "termcolor" version = "2.3.0" description = "ANSI color formatting for output in terminal" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4462,6 +4699,7 @@ tests = ["pytest", "pytest-cov"] name = "threadpoolctl" version = "3.2.0" description = "threadpoolctl" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4473,6 +4711,7 @@ files = [ name = "tinycss2" version = "1.2.1" description = "A tiny CSS parser" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4491,6 +4730,7 @@ test = ["flake8", "isort", "pytest"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4502,6 +4742,7 @@ files = [ name = "tomlkit" version = "0.12.1" description = "Style preserving TOML library" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4513,6 +4754,7 @@ files = [ name = "tornado" version = "6.3.3" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +category = "dev" optional = false python-versions = ">= 3.8" files = [ @@ -4533,6 +4775,7 @@ files = [ name = "tqdm" version = "4.66.1" description = "Fast, Extensible Progress Meter" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4553,6 +4796,7 @@ telegram = ["requests"] name = "traitlets" version = "5.12.0" description = "Traitlets Python configuration system" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4568,6 +4812,7 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.6.0)", "pre-commit", "pytest (>=7.0, name = "types-deprecated" version = "1.2.9.3" description = "Typing stubs for Deprecated" +category = "dev" optional = false python-versions = "*" files = [ @@ -4579,6 +4824,7 @@ files = [ name = "types-python-dateutil" version = "2.8.19.14" description = "Typing stubs for python-dateutil" +category = "dev" optional = false python-versions = "*" files = [ @@ -4590,6 +4836,7 @@ files = [ name = "types-retry" version = "0.9.9.4" description = "Typing stubs for retry" +category = "dev" optional = false python-versions = "*" files = [ @@ -4601,6 +4848,7 @@ files = [ name = "typing-extensions" version = "4.8.0" description = "Backported and Experimental Type Hints for Python 3.8+" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4612,6 +4860,7 @@ files = [ name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" +category = "dev" optional = false python-versions = ">=2" files = [ @@ -4623,6 +4872,7 @@ files = [ name = "urllib3" version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4640,6 +4890,7 @@ zstd = ["zstandard (>=0.18.0)"] name = "wcwidth" version = "0.2.8" description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" optional = false python-versions = "*" files = [ @@ -4651,6 +4902,7 @@ files = [ name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" +category = "dev" optional = false python-versions = "*" files = [ @@ -4662,6 +4914,7 @@ files = [ name = "werkzeug" version = "3.0.1" description = "The comprehensive WSGI web application library." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4677,13 +4930,14 @@ watchdog = ["watchdog (>=2.3)"] [[package]] name = "wheel" -version = "0.41.2" +version = "0.41.3" description = "A built-package format for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "wheel-0.41.2-py3-none-any.whl", hash = "sha256:75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8"}, - {file = "wheel-0.41.2.tar.gz", hash = "sha256:0c5ac5ff2afb79ac23ab82bab027a0be7b5dbcf2e54dc50efe4bf507de1f7985"}, + {file = "wheel-0.41.3-py3-none-any.whl", hash = "sha256:488609bc63a29322326e05560731bf7bfea8e48ad646e1f5e40d366607de0942"}, + {file = "wheel-0.41.3.tar.gz", hash = "sha256:4d4987ce51a49370ea65c0bfd2234e8ce80a12780820d9dc462597a6e60d0841"}, ] [package.extras] @@ -4693,6 +4947,7 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] name = "wrapt" version = "1.14.1" description = "Module for decorators, wrappers and monkey patching." +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -4715,6 +4970,16 @@ files = [ {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecee4132c6cd2ce5308e21672015ddfed1ff975ad0ac8d27168ea82e71413f55"}, + {file = "wrapt-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2020f391008ef874c6d9e208b24f28e31bcb85ccff4f335f15a3251d222b92d9"}, + {file = "wrapt-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2feecf86e1f7a86517cab34ae6c2f081fd2d0dac860cb0c0ded96d799d20b335"}, + {file = "wrapt-1.14.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:240b1686f38ae665d1b15475966fe0472f78e71b1b4903c143a842659c8e4cb9"}, + {file = "wrapt-1.14.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9008dad07d71f68487c91e96579c8567c98ca4c3881b9b113bc7b33e9fd78b8"}, + {file = "wrapt-1.14.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6447e9f3ba72f8e2b985a1da758767698efa72723d5b59accefd716e9e8272bf"}, + {file = "wrapt-1.14.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:acae32e13a4153809db37405f5eba5bac5fbe2e2ba61ab227926a22901051c0a"}, + {file = "wrapt-1.14.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49ef582b7a1152ae2766557f0550a9fcbf7bbd76f43fbdc94dd3bf07cc7168be"}, + {file = "wrapt-1.14.1-cp311-cp311-win32.whl", hash = "sha256:358fe87cc899c6bb0ddc185bf3dbfa4ba646f05b1b0b9b5a27c2cb92c2cea204"}, + {file = "wrapt-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:26046cd03936ae745a502abf44dac702a5e6880b2b01c29aea8ddf3353b68224"}, {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, @@ -4766,6 +5031,7 @@ files = [ name = "zipp" version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4780,4 +5046,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12" -content-hash = "b506ce19e860956a3a4437620519237f1680fddc6488a206ff58017ed4faf958" +content-hash = "ea317481a805dfd818291da4260d697e6c5da004d8daeeecdabcaeef21ebda5b" diff --git a/pyproject.toml b/pyproject.toml index 6966741742..1280ba36ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ joblib = "^1.2.0" matplotlib = "^3.7.0" psutil = "^5.9.4" tabulate = "^0.9.0" -more-itertools = ">=9.1.0" +more-itertools = "^9.1.0" [tool.poetry.group.docs] optional = true From 16deac55bbbc1e2d3a63e9fb6ef0e127353bf834 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 30 Oct 2023 14:28:07 +0400 Subject: [PATCH 07/55] move NativeType to qibo --- src/qibo/transpiler/abstract.py | 2 +- src/qibo/transpiler/pipeline.py | 1 + src/qibo/transpiler/unroller.py | 24 +++++++++++++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py index b0638e8fd6..003b4c8e5f 100644 --- a/src/qibo/transpiler/abstract.py +++ b/src/qibo/transpiler/abstract.py @@ -2,11 +2,11 @@ from typing import Tuple import networkx as nx -from qibolab.native import NativeType from qibo import gates from qibo.config import raise_error from qibo.models import Circuit +from qibo.transpiler.unroller import NativeType def find_gates_qubits_pairs(circuit: Circuit): diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index a663ddbaef..ab51e624a4 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -13,6 +13,7 @@ from qibo.transpiler.unroller import ( DecompositionError, NativeGates, + NativeType, assert_decomposition, ) diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index d20139a393..a4732ddfce 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -1,5 +1,6 @@ +from enum import Flag, auto + import numpy as np -from qibolab.native import NativeType from qibo import gates from qibo.backends import NumpyBackend @@ -14,6 +15,27 @@ backend = NumpyBackend() +class NativeType(Flag): + """Define available types of native gates. + + Should have the same names with qibo gates. + """ + + M = auto() + Z = auto() + RZ = auto() + GPI2 = auto() + CZ = auto() + iSWAP = auto() + + @classmethod + def from_gate(cls, gate: gates.Gate): + try: + return getattr(cls, gate.__class__.__name__) + except AttributeError: + raise ValueError(f"Gate {gate} cannot be used as native.") + + # TODO: Make setting single-qubit native gates more flexible class NativeGates(Unroller): """Translates a circuit to native gates. From 6ce000e849948e1b7b46e178e530159da1724d8f Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 30 Oct 2023 14:34:54 +0400 Subject: [PATCH 08/55] NativeType --- src/qibo/transpiler/pipeline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index ab51e624a4..0bf529bcab 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -1,6 +1,5 @@ import networkx as nx import numpy as np -from qibolab.native import NativeType from qibo.backends import NumpyBackend from qibo.models import Circuit From ea05c169c572d7afbe3cc6330bd6a66c27411e50 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 30 Oct 2023 14:47:15 +0400 Subject: [PATCH 09/55] renamed and update dependency --- ...est_transpilers_abstract.py => test_transpiler_abstract.py} | 0 .../{test_transpilers_blocks.py => test_transpiler_blocks.py} | 0 ...t_transpilers_optimizer.py => test_transpiler_optimizer.py} | 0 ...est_transpilers_pipeline.py => test_transpiler_pipeline.py} | 3 +-- .../{test_transpilers_placer.py => test_transpiler_placer.py} | 0 .../{test_transpilers_router.py => test_transpiler_router.py} | 0 ...ar_connectivity.py => test_transpiler_star_connectivity.py} | 0 ...mpositions.py => test_transpiler_unitary_decompositions.py} | 0 ...est_transpilers_unroller.py => test_transpiler_unroller.py} | 2 +- 9 files changed, 2 insertions(+), 3 deletions(-) rename tests/{test_transpilers_abstract.py => test_transpiler_abstract.py} (100%) rename tests/{test_transpilers_blocks.py => test_transpiler_blocks.py} (100%) rename tests/{test_transpilers_optimizer.py => test_transpiler_optimizer.py} (100%) rename tests/{test_transpilers_pipeline.py => test_transpiler_pipeline.py} (98%) rename tests/{test_transpilers_placer.py => test_transpiler_placer.py} (100%) rename tests/{test_transpilers_router.py => test_transpiler_router.py} (100%) rename tests/{test_transpilers_star_connectivity.py => test_transpiler_star_connectivity.py} (100%) rename tests/{test_transpilers_unitary_decompositions.py => test_transpiler_unitary_decompositions.py} (100%) rename tests/{test_transpilers_unroller.py => test_transpiler_unroller.py} (99%) diff --git a/tests/test_transpilers_abstract.py b/tests/test_transpiler_abstract.py similarity index 100% rename from tests/test_transpilers_abstract.py rename to tests/test_transpiler_abstract.py diff --git a/tests/test_transpilers_blocks.py b/tests/test_transpiler_blocks.py similarity index 100% rename from tests/test_transpilers_blocks.py rename to tests/test_transpiler_blocks.py diff --git a/tests/test_transpilers_optimizer.py b/tests/test_transpiler_optimizer.py similarity index 100% rename from tests/test_transpilers_optimizer.py rename to tests/test_transpiler_optimizer.py diff --git a/tests/test_transpilers_pipeline.py b/tests/test_transpiler_pipeline.py similarity index 98% rename from tests/test_transpilers_pipeline.py rename to tests/test_transpiler_pipeline.py index f5facb2af7..c37007a6ba 100644 --- a/tests/test_transpilers_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -3,7 +3,6 @@ import networkx as nx import numpy as np import pytest -from qibolab.native import NativeType from qibo import gates from qibo.models import Circuit @@ -16,7 +15,7 @@ ) from qibo.transpiler.placer import Random, ReverseTraversal, Trivial from qibo.transpiler.router import ShortestPaths -from qibo.transpiler.unroller import NativeGates +from qibo.transpiler.unroller import NativeGates, NativeType def generate_random_circuit(nqubits, ngates, seed=None): diff --git a/tests/test_transpilers_placer.py b/tests/test_transpiler_placer.py similarity index 100% rename from tests/test_transpilers_placer.py rename to tests/test_transpiler_placer.py diff --git a/tests/test_transpilers_router.py b/tests/test_transpiler_router.py similarity index 100% rename from tests/test_transpilers_router.py rename to tests/test_transpiler_router.py diff --git a/tests/test_transpilers_star_connectivity.py b/tests/test_transpiler_star_connectivity.py similarity index 100% rename from tests/test_transpilers_star_connectivity.py rename to tests/test_transpiler_star_connectivity.py diff --git a/tests/test_transpilers_unitary_decompositions.py b/tests/test_transpiler_unitary_decompositions.py similarity index 100% rename from tests/test_transpilers_unitary_decompositions.py rename to tests/test_transpiler_unitary_decompositions.py diff --git a/tests/test_transpilers_unroller.py b/tests/test_transpiler_unroller.py similarity index 99% rename from tests/test_transpilers_unroller.py rename to tests/test_transpiler_unroller.py index 23b234cf32..82f9ce5065 100644 --- a/tests/test_transpilers_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -1,6 +1,5 @@ import numpy as np import pytest -from qibolab.native import NativeType from qibo import gates from qibo.backends import NumpyBackend @@ -8,6 +7,7 @@ from qibo.transpiler.unroller import ( DecompositionError, NativeGates, + NativeType, assert_decomposition, translate_gate, ) From ecda53aadfa753b65bca05720d0e191b53219f86 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 30 Oct 2023 15:02:09 +0400 Subject: [PATCH 10/55] move NativeType to abstract --- src/qibo/transpiler/abstract.py | 23 +++++++++++++++++++- src/qibo/transpiler/pipeline.py | 3 +-- src/qibo/transpiler/unroller.py | 25 +--------------------- tests/test_transpiler_pipeline.py | 3 ++- tests/test_transpiler_star_connectivity.py | 2 +- tests/test_transpiler_unroller.py | 6 +++--- 6 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py index 003b4c8e5f..6bef0e78a3 100644 --- a/src/qibo/transpiler/abstract.py +++ b/src/qibo/transpiler/abstract.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from enum import Flag, auto from typing import Tuple import networkx as nx @@ -6,7 +7,27 @@ from qibo import gates from qibo.config import raise_error from qibo.models import Circuit -from qibo.transpiler.unroller import NativeType + + +class NativeType(Flag): + """Define available types of native gates. + + Should have the same names with qibo gates. + """ + + M = auto() + Z = auto() + RZ = auto() + GPI2 = auto() + CZ = auto() + iSWAP = auto() + + @classmethod + def from_gate(cls, gate: gates.Gate): + try: + return getattr(cls, gate.__class__.__name__) + except AttributeError: + raise ValueError(f"Gate {gate} cannot be used as native.") def find_gates_qubits_pairs(circuit: Circuit): diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index 0bf529bcab..56a71e96bb 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -4,7 +4,7 @@ from qibo.backends import NumpyBackend from qibo.models import Circuit from qibo.quantum_info.random_ensembles import random_statevector -from qibo.transpiler.abstract import Optimizer, Placer, Router, Unroller +from qibo.transpiler.abstract import NativeType, Optimizer, Placer, Router, Unroller from qibo.transpiler.optimizer import Preprocessing from qibo.transpiler.placer import Trivial, assert_placement from qibo.transpiler.router import ConnectivityError, assert_connectivity @@ -12,7 +12,6 @@ from qibo.transpiler.unroller import ( DecompositionError, NativeGates, - NativeType, assert_decomposition, ) diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index a4732ddfce..6d8fb72a77 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -1,12 +1,10 @@ -from enum import Flag, auto - import numpy as np from qibo import gates from qibo.backends import NumpyBackend from qibo.config import raise_error from qibo.models import Circuit -from qibo.transpiler.abstract import Unroller +from qibo.transpiler.abstract import NativeType, Unroller from qibo.transpiler.unitary_decompositions import ( two_qubit_decomposition, u3_decomposition, @@ -15,27 +13,6 @@ backend = NumpyBackend() -class NativeType(Flag): - """Define available types of native gates. - - Should have the same names with qibo gates. - """ - - M = auto() - Z = auto() - RZ = auto() - GPI2 = auto() - CZ = auto() - iSWAP = auto() - - @classmethod - def from_gate(cls, gate: gates.Gate): - try: - return getattr(cls, gate.__class__.__name__) - except AttributeError: - raise ValueError(f"Gate {gate} cannot be used as native.") - - # TODO: Make setting single-qubit native gates more flexible class NativeGates(Unroller): """Translates a circuit to native gates. diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index c37007a6ba..f887d4b964 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -6,6 +6,7 @@ from qibo import gates from qibo.models import Circuit +from qibo.transpiler.abstract import NativeType from qibo.transpiler.optimizer import Preprocessing from qibo.transpiler.pipeline import ( Passes, @@ -15,7 +16,7 @@ ) from qibo.transpiler.placer import Random, ReverseTraversal, Trivial from qibo.transpiler.router import ShortestPaths -from qibo.transpiler.unroller import NativeGates, NativeType +from qibo.transpiler.unroller import NativeGates def generate_random_circuit(nqubits, ngates, seed=None): diff --git a/tests/test_transpiler_star_connectivity.py b/tests/test_transpiler_star_connectivity.py index e963a00116..e51e0792e2 100644 --- a/tests/test_transpiler_star_connectivity.py +++ b/tests/test_transpiler_star_connectivity.py @@ -66,7 +66,7 @@ def test_fix_connectivity_unitaries(nqubits, unitary_dim, depth, middle_qubit): """Checks that the transpiled circuit can be executed and is equivalent to original when using unitaries.""" - from .test_transpilers_unitary_decompositions import random_unitary + from .test_transpiler_unitary_decompositions import random_unitary # find the number of qubits for hardware circuit n_hardware_qubits = max(nqubits, middle_qubit + 1) diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index 82f9ce5065..c71050a4f8 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -4,10 +4,10 @@ from qibo import gates from qibo.backends import NumpyBackend from qibo.models import Circuit +from qibo.transpiler.abstract import NativeType from qibo.transpiler.unroller import ( DecompositionError, NativeGates, - NativeType, assert_decomposition, translate_gate, ) @@ -135,7 +135,7 @@ def test_fSim_to_native(natives): [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], ) def test_GeneralizedfSim_to_native(natives): - from .test_transpilers_unitary_decompositions import random_unitary + from .test_transpiler_unitary_decompositions import random_unitary unitary = random_unitary(1) gate = gates.GeneralizedfSim(0, 1, unitary, phi=0.1) @@ -167,7 +167,7 @@ def test_TOFFOLI_to_native(natives): ) @pytest.mark.parametrize("nqubits", [1, 2]) def test_unitary_to_native(nqubits, natives): - from .test_transpilers_unitary_decompositions import random_unitary + from .test_transpiler_unitary_decompositions import random_unitary u = random_unitary(nqubits) # transform to SU(2^nqubits) form From e50530938ec0dd4ca893479c563e792fb927b654 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 30 Oct 2023 15:12:07 +0400 Subject: [PATCH 11/55] remove last dependencies --- tests/test_transpiler_unroller.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index c71050a4f8..d76f5365f0 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -15,7 +15,6 @@ def assert_matrices_allclose(gate, two_qubit_natives): backend = NumpyBackend() - native_gates = translate_gate(gate, two_qubit_natives) target_matrix = gate.matrix(backend) # Remove global phase from target matrix target_unitary = target_matrix / np.power( @@ -177,14 +176,14 @@ def test_unitary_to_native(nqubits, natives): def test_count_1q(): - from qibolab.transpilers.unroller import cz_dec + from qibo.transpiler.unroller import cz_dec np.testing.assert_allclose(cz_dec.count_1q(gates.CNOT(0, 1)), 2) np.testing.assert_allclose(cz_dec.count_1q(gates.CRX(0, 1, 0.1)), 2) def test_count_2q(): - from qibolab.transpilers.unroller import cz_dec + from qibo.transpiler.unroller import cz_dec np.testing.assert_allclose(cz_dec.count_2q(gates.CNOT(0, 1)), 1) np.testing.assert_allclose(cz_dec.count_2q(gates.CRX(0, 1, 0.1)), 2) From 46939102d3eb48aed93d36430745513ccd1eabd8 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 30 Oct 2023 15:28:16 +0400 Subject: [PATCH 12/55] fixed tests --- tests/test_transpiler_blocks.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_transpiler_blocks.py b/tests/test_transpiler_blocks.py index 1c44aa9c46..b2075bcbf3 100644 --- a/tests/test_transpiler_blocks.py +++ b/tests/test_transpiler_blocks.py @@ -214,7 +214,10 @@ def test_circuit_blocks(): assert block.name == index reconstructed_circ = circuit_blocks.circuit() # Here we can't use assert gates_equality because the order of the gates is changed - np.testing.assert_allclose(circ(), reconstructed_circ()) + np.testing.assert_allclose( + np.array(circ().state(), dtype=float), + np.array(reconstructed_circ().state(), dtype=float), + ) first_block = circuit_blocks.search_by_index(0) assert_gates_equality( first_block.gates, From d1a27717aa3e7288e3e25682b7a310f1ea7fe17c Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 31 Oct 2023 17:29:23 +0400 Subject: [PATCH 13/55] circuit blocks working with measurements --- src/qibo/transpiler/blocks.py | 38 +++++++++++--- src/qibo/transpiler/router.py | 8 +++ tests/test_transpiler_blocks.py | 87 +++++++++++++++++++++++++++++++-- 3 files changed, 122 insertions(+), 11 deletions(-) diff --git a/src/qibo/transpiler/blocks.py b/src/qibo/transpiler/blocks.py index b3be3eaf66..b701b7c2ab 100644 --- a/src/qibo/transpiler/blocks.py +++ b/src/qibo/transpiler/blocks.py @@ -1,6 +1,6 @@ from typing import Optional, Union -from qibo import Circuit +from qibo import Circuit, gates from qibo.gates import Gate @@ -29,6 +29,7 @@ def __init__( @property def entangled(self): + """True if the block contains two qubit gates.""" return self.count_2q_gates() > 0 def rename(self, name): @@ -164,8 +165,29 @@ def remove_block(self, block: "Block"): ) +def check_multi_qubit_measurements(circuit: Circuit): + """Return True if the block contains measurements acting on multiple qubits""" + for gate in circuit.queue: + if isinstance(gate, gates.M) and len(gate.qubits) > 1: + return True + return False + + +def split_multi_qubit_measurements(circuit: Circuit): + """Return an equivalent circuit containinig measurements acting only on single qubits""" + new_circuit = Circuit(circuit.nqubits) + for gate in circuit.queue: + if isinstance(gate, gates.M) and len(gate.qubits) > 1: + for qubit in gate.qubits: + circuit.add(gates.M(qubit)) + else: + new_circuit.add(gate) + return new_circuit + + def block_decomposition(circuit: Circuit, fuse: bool = True): """Decompose a circuit into blocks of gates acting on two qubits. + Break measurements on multiple qubits into measurements of single qubit. Args: circuit (qibo.models.Circuit): circuit to be decomposed. @@ -178,6 +200,8 @@ def block_decomposition(circuit: Circuit, fuse: bool = True): raise BlockingError( "Only circuits with at least two qubits can be decomposed with block_decomposition." ) + if check_multi_qubit_measurements(circuit): + circuit = split_multi_qubit_measurements(circuit) initial_blocks = initial_block_decomposition(circuit) if not fuse: return initial_blocks @@ -210,8 +234,7 @@ def initial_block_decomposition(circuit: Circuit): """ blocks = [] all_gates = list(circuit.queue) - two_qubit_gates = count_multi_qubit_gates(all_gates) - while two_qubit_gates > 0: + while count_multi_qubit_gates(all_gates) > 0: for idx, gate in enumerate(all_gates): if len(gate.qubits) == 2: qubits = gate.qubits @@ -220,7 +243,6 @@ def initial_block_decomposition(circuit: Circuit): block_gates.extend(_find_successive_gates(all_gates[idx + 1 :], qubits)) block = Block(qubits=qubits, gates=block_gates) remove_gates(all_gates, block_gates) - two_qubit_gates -= 1 blocks.append(block) break elif len(gate.qubits) > 2: @@ -276,11 +298,11 @@ def count_multi_qubit_gates(gatelist: list): return len([gate for gate in gatelist if len(gate.qubits) >= 2]) -def _find_successive_gates(gates: list, qubits: tuple): +def _find_successive_gates(gates_list: list, qubits: tuple): """Return a list containing all gates acting on qubits until a new two qubit gate acting on qubits is found.""" successive_gates = [] for qubit in qubits: - for gate in gates: + for gate in gates_list: if (len(gate.qubits) == 1) and (gate.qubits[0] == qubit): successive_gates.append(gate) elif (len(gate.qubits) == 2) and (qubit in gate.qubits): @@ -288,10 +310,10 @@ def _find_successive_gates(gates: list, qubits: tuple): return successive_gates -def _find_previous_gates(gates: list, qubits: tuple): +def _find_previous_gates(gates_list: list, qubits: tuple): """Return a list containing all gates acting on qubits.""" previous_gates = [] - for gate in gates: + for gate in gates_list: if gate.qubits[0] in qubits: previous_gates.append(gate) return previous_gates diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 3abae571c0..fb5a5b9366 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -468,9 +468,17 @@ def __call__(self, circuit, initial_layout): Returns: (qibo.models.Circuit): routed circuit. """ + DEBUG = False self.preprocessing(circuit=circuit, initial_layout=initial_layout) + if DEBUG: + draw_dag(self._dag, "dag") while self._dag.number_of_nodes() != 0: execute_block_list = self.check_execution() + if DEBUG: + print("front_layer") + print(self._front_layer) + print("executables") + print(execute_block_list) if execute_block_list is not None: self.execute_blocks(execute_block_list) else: diff --git a/tests/test_transpiler_blocks.py b/tests/test_transpiler_blocks.py index b2075bcbf3..a35d4b2187 100644 --- a/tests/test_transpiler_blocks.py +++ b/tests/test_transpiler_blocks.py @@ -9,10 +9,12 @@ _find_previous_gates, _find_successive_gates, block_decomposition, + check_multi_qubit_measurements, count_multi_qubit_gates, gates_on_qubit, initial_block_decomposition, remove_gates, + split_multi_qubit_measurements, ) @@ -126,6 +128,48 @@ def test_initial_block_decomposition(): assert len(blocks[3].gates) == 2 +def test_check_measurements(): + circ = Circuit(2) + circ.add(gates.H(1)) + circ.add(gates.M(0, 1)) + assert check_multi_qubit_measurements(circ) + circ = Circuit(2) + circ.add(gates.H(1)) + circ.add(gates.M(0)) + circ.add(gates.M(1)) + assert not check_multi_qubit_measurements(circ) + + +def test_split_measurements(): + circ = Circuit(2) + circ.add(gates.H(1)) + circ.add(gates.M(0, 1)) + new_circ = split_multi_qubit_measurements(circ) + assert_gates_equality(new_circ.queue, [gates.H(1), gates.M(0), gates.M(1)]) + + +def test_initial_block_decomposition_measurements(): + circ = Circuit(5) + circ.add(gates.M(0)) + circ.add(gates.M(1)) + circ.add(gates.H(1)) + circ.add(gates.H(0)) + circ.add(gates.CZ(0, 1)) + circ.add(gates.CZ(0, 1)) + circ.add(gates.M(1)) + circ.add(gates.M(3)) + circ.add(gates.H(3)) + circ.add(gates.H(4)) + blocks = initial_block_decomposition(circ) + print(blocks[0].gates) + assert_gates_equality( + blocks[0].gates, + [gates.M(0), gates.M(1), gates.H(1), gates.H(0), gates.CZ(0, 1)], + ) + assert_gates_equality(blocks[1].gates, [gates.CZ(0, 1), gates.M(1)]) + assert_gates_equality(blocks[2].gates, [gates.M(3), gates.H(3), gates.H(4)]) + + def test_initial_block_decomposition_error(): circ = Circuit(3) circ.add(gates.TOFFOLI(0, 1, 2)) @@ -144,6 +188,7 @@ def test_block_decomposition_no_fuse(): circ = Circuit(4) circ.add(gates.H(1)) circ.add(gates.H(0)) + circ.add(gates.H(0)) circ.add(gates.CZ(0, 1)) circ.add(gates.H(0)) circ.add(gates.CZ(0, 1)) @@ -152,10 +197,11 @@ def test_block_decomposition_no_fuse(): circ.add(gates.H(3)) blocks = block_decomposition(circ, fuse=False) assert_gates_equality( - blocks[0].gates, [gates.H(1), gates.H(0), gates.CZ(0, 1), gates.H(0)] + blocks[0].gates, + [gates.H(1), gates.H(0), gates.H(0), gates.CZ(0, 1), gates.H(0)], ) assert len(blocks) == 4 - assert len(blocks[0].gates) == 4 + assert len(blocks[0].gates) == 5 assert len(blocks[1].gates) == 1 assert blocks[2].entangled == True assert blocks[3].entangled == False @@ -194,6 +240,39 @@ def test_block_decomposition(): assert len(blocks[2].gates) == 3 +def test_block_decomposition_measurements(): + circ = Circuit(4) + circ.add(gates.H(1)) # first block + circ.add(gates.H(0)) # first block + circ.add(gates.CZ(0, 1)) # first block + circ.add(gates.H(0)) # first block + circ.add(gates.M(0, 1)) # first block + circ.add(gates.CZ(1, 2)) # second block + circ.add(gates.CZ(1, 2)) # second block + circ.add(gates.H(1)) # second block + circ.add(gates.H(3)) # 4 block + circ.add(gates.CZ(0, 1)) # 3 block + circ.add(gates.CZ(0, 1)) # 3 block + circ.add(gates.CZ(2, 3)) # 4 block + circ.add(gates.M(0, 1)) # 3 block + blocks = block_decomposition(circ) + assert_gates_equality( + blocks[0].gates, + [gates.H(1), gates.H(0), gates.CZ(0, 1), gates.H(0), gates.M(0), gates.M(1)], + ) + assert len(blocks) == 4 + assert blocks[0].count_2q_gates() == 1 + assert len(blocks[0].gates) == 5 + assert blocks[0].qubits == (0, 1) + assert blocks[1].count_2q_gates() == 2 + assert len(blocks[1].gates) == 3 + assert blocks[3].count_2q_gates() == 1 + assert len(blocks[3].gates) == 2 + assert blocks[3].qubits == (2, 3) + assert blocks[2].count_2q_gates() == 2 + assert len(blocks[2].gates) == 3 + + def test_circuit_blocks(): circ = Circuit(4) circ.add(gates.H(1)) @@ -289,10 +368,12 @@ def test_search_by_index_error_no_index_found(): def test_block_on_qubits(): block = Block( - qubits=(0, 1), gates=[gates.H(0), gates.CZ(0, 1), gates.H(1), gates.CZ(1, 0)] + qubits=(0, 1), + gates=[gates.H(0), gates.CZ(0, 1), gates.H(1), gates.CZ(1, 0), gates.M(1)], ) new_block = block.on_qubits(new_qubits=(2, 3)) assert new_block.gates[0].qubits == (2,) assert new_block.gates[1].qubits == (2, 3) assert new_block.gates[2].qubits == (3,) assert new_block.gates[3].qubits == (3, 2) + assert new_block.gates[4].qubits == (3,) From 147bb8d821a996814a88d845dd526ec62d460526 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Wed, 1 Nov 2023 11:37:49 +0400 Subject: [PATCH 14/55] improve coverage --- src/qibo/transpiler/blocks.py | 2 +- src/qibo/transpiler/router.py | 8 --- src/qibo/transpiler/star_connectivity.py | 51 +++---------------- tests/test_transpiler_blocks.py | 5 +- tests/test_transpiler_router.py | 9 ++-- tests/test_transpiler_star_connectivity.py | 27 ++++++---- .../test_transpiler_unitary_decompositions.py | 16 +++++- 7 files changed, 48 insertions(+), 70 deletions(-) diff --git a/src/qibo/transpiler/blocks.py b/src/qibo/transpiler/blocks.py index b701b7c2ab..bccdf25487 100644 --- a/src/qibo/transpiler/blocks.py +++ b/src/qibo/transpiler/blocks.py @@ -179,7 +179,7 @@ def split_multi_qubit_measurements(circuit: Circuit): for gate in circuit.queue: if isinstance(gate, gates.M) and len(gate.qubits) > 1: for qubit in gate.qubits: - circuit.add(gates.M(qubit)) + new_circuit.add(gates.M(qubit)) else: new_circuit.add(gate) return new_circuit diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index fb5a5b9366..3abae571c0 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -468,17 +468,9 @@ def __call__(self, circuit, initial_layout): Returns: (qibo.models.Circuit): routed circuit. """ - DEBUG = False self.preprocessing(circuit=circuit, initial_layout=initial_layout) - if DEBUG: - draw_dag(self._dag, "dag") while self._dag.number_of_nodes() != 0: execute_block_list = self.check_execution() - if DEBUG: - print("front_layer") - print(self._front_layer) - print("executables") - print(execute_block_list) if execute_block_list is not None: self.execute_blocks(execute_block_list) else: diff --git a/src/qibo/transpiler/star_connectivity.py b/src/qibo/transpiler/star_connectivity.py index 110b243178..29b5d661da 100644 --- a/src/qibo/transpiler/star_connectivity.py +++ b/src/qibo/transpiler/star_connectivity.py @@ -1,6 +1,7 @@ -from qibo import gates -from qibo.config import log, raise_error +from qibo import Circuit, gates +from qibo.backends import NumpyBackend from qibo.transpiler.abstract import Router +from qibo.transpiler.router import ConnectivityError def find_connected_qubit(qubits, queue, hardware_qubits): @@ -40,45 +41,10 @@ class StarConnectivity(Router): verbose (bool): print info messages. """ - def __init__(self, connectivity=None, middle_qubit=2, verbose=False): + def __init__(self, connectivity=None, middle_qubit=2): self.middle_qubit = middle_qubit - self.verbose = verbose - def tlog(self, message): - """Print messages only if ``verbose`` was set to ``True``.""" - if self.verbose: - log.info(message) - - def is_satisfied(self, circuit): - """Checks if a circuit respects connectivity constraints. - - Args: - circuit (qibo.models.Circuit): Circuit model to check. - middle_qubit (int): Hardware middle qubit. - verbose (bool): If ``True`` it prints debugging log messages. - - Returns ``True`` if the following conditions are satisfied: - - Circuit does not contain more than two-qubit gates. - - All two-qubit gates have qubit 0 as target or control. - - otherwise returns ``False``. - """ - for gate in circuit.queue: - if len(gate.qubits) > 2 and not isinstance(gate, gates.M): - self.tlog(f"{gate.name} acts on more than two qubits.") - return False - elif len(gate.qubits) == 2: - if self.middle_qubit not in gate.qubits: - self.tlog( - "Circuit does not respect connectivity. " - f"{gate.name} acts on {gate.qubits}." - ) - return False - - self.tlog("Circuit respects connectivity.") - return True - - def __call__(self, circuit, initial_layout=None): + def __call__(self, circuit: Circuit, initial_layout=None): """Apply the transpiler transformation on a given circuit. Args: @@ -128,9 +94,8 @@ def __call__(self, circuit, initial_layout=None): continue if len(qubits) > 2: - raise_error( - NotImplementedError, - "Transpiler does not support gates targeting more than two-qubits.", + raise ConnectivityError( + "Gates targeting more than two qubits are not supported." ) elif len(qubits) == 2 and middle_qubit not in qubits: @@ -151,8 +116,6 @@ def __call__(self, circuit, initial_layout=None): # add gate to the hardware circuit if isinstance(gate, gates.Unitary): # gates.Unitary requires matrix as first argument - from qibo.backends import NumpyBackend - backend = NumpyBackend() matrix = gate.matrix(backend) new.add(gate.__class__(matrix, *qubits, **gate.init_kwargs)) diff --git a/tests/test_transpiler_blocks.py b/tests/test_transpiler_blocks.py index a35d4b2187..46a605ddb2 100644 --- a/tests/test_transpiler_blocks.py +++ b/tests/test_transpiler_blocks.py @@ -256,13 +256,14 @@ def test_block_decomposition_measurements(): circ.add(gates.CZ(2, 3)) # 4 block circ.add(gates.M(0, 1)) # 3 block blocks = block_decomposition(circ) + print(blocks[0].gates) assert_gates_equality( blocks[0].gates, [gates.H(1), gates.H(0), gates.CZ(0, 1), gates.H(0), gates.M(0), gates.M(1)], ) assert len(blocks) == 4 assert blocks[0].count_2q_gates() == 1 - assert len(blocks[0].gates) == 5 + assert len(blocks[0].gates) == 6 assert blocks[0].qubits == (0, 1) assert blocks[1].count_2q_gates() == 2 assert len(blocks[1].gates) == 3 @@ -270,7 +271,7 @@ def test_block_decomposition_measurements(): assert len(blocks[3].gates) == 2 assert blocks[3].qubits == (2, 3) assert blocks[2].count_2q_gates() == 2 - assert len(blocks[2].gates) == 3 + assert len(blocks[2].gates) == 4 def test_circuit_blocks(): diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 3219acb5c2..0aff133dc4 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -132,15 +132,18 @@ def test_incorrect_initial_layout(): transpiler(circuit, initial_layout) -@pytest.mark.parametrize("gates", [10, 30]) +@pytest.mark.parametrize("gates", [5, 25]) @pytest.mark.parametrize("qubits", [3, 5]) @pytest.mark.parametrize("placer", [Trivial, Random]) @pytest.mark.parametrize("connectivity", [star_connectivity(), grid_connectivity()]) -def test_random_circuits_5q(gates, qubits, placer, connectivity): +@pytest.mark.parametrize("split", [1.0, 0.5]) +def test_random_circuits_5q(gates, qubits, placer, connectivity, split): placer = placer(connectivity=connectivity) layout_circ = Circuit(5) initial_layout = placer(layout_circ) - transpiler = ShortestPaths(connectivity=connectivity, verbose=True) + transpiler = ShortestPaths( + connectivity=connectivity, verbose=True, sampling_split=split + ) circuit = generate_random_circuit(nqubits=qubits, ngates=gates) transpiled_circuit, final_qubit_map = transpiler(circuit, initial_layout) assert transpiler.added_swaps >= 0 diff --git a/tests/test_transpiler_star_connectivity.py b/tests/test_transpiler_star_connectivity.py index e51e0792e2..ff0d177915 100644 --- a/tests/test_transpiler_star_connectivity.py +++ b/tests/test_transpiler_star_connectivity.py @@ -7,8 +7,11 @@ from qibo.backends import NumpyBackend from qibo.models import Circuit from qibo.transpiler.pipeline import transpose_qubits +from qibo.transpiler.router import ConnectivityError from qibo.transpiler.star_connectivity import StarConnectivity +from .test_transpiler_unitary_decompositions import random_unitary + def generate_random_circuit(nqubits, depth, seed=None, middle_qubit=2): """Generate random circuits one-qubit rotations and CZ gates.""" @@ -38,18 +41,25 @@ def generate_random_circuit(nqubits, depth, seed=None, middle_qubit=2): return circuit -@pytest.mark.parametrize("verbose", [True, False]) +def test_error_multi_qubit(): + circuit = Circuit(3) + circuit.add(gates.TOFFOLI(0, 1, 2)) + transpiler = StarConnectivity(middle_qubit=2) + with pytest.raises(ConnectivityError): + transpiled, hardware_qubits = transpiler(circuit) + + @pytest.mark.parametrize("nqubits", [1, 2, 3, 4, 5]) @pytest.mark.parametrize("middle_qubit", [0, 1, 2, 3, 4]) @pytest.mark.parametrize("depth", [2, 10]) -def test_fix_connectivity(nqubits, depth, middle_qubit, verbose): +@pytest.mark.parametrize("measurements", [True, False]) +def test_fix_connectivity(nqubits, depth, middle_qubit, measurements): """Checks that the transpiled circuit can be executed and is equivalent to original.""" original = generate_random_circuit(nqubits, depth, middle_qubit=middle_qubit) - transpiler = StarConnectivity(middle_qubit=middle_qubit, verbose=verbose) + if measurements: + original.add(gates.M(0)) + transpiler = StarConnectivity(middle_qubit=middle_qubit) transpiled, hardware_qubits = transpiler(original) - # check that transpiled circuit can be executed - assert transpiler.is_satisfied(transpiled) - # check that execution results agree with original (using simulation) backend = NumpyBackend() final_state = backend.execute_circuit(transpiled).state() target_state = backend.execute_circuit(original).state() @@ -65,9 +75,6 @@ def test_fix_connectivity(nqubits, depth, middle_qubit, verbose): def test_fix_connectivity_unitaries(nqubits, unitary_dim, depth, middle_qubit): """Checks that the transpiled circuit can be executed and is equivalent to original when using unitaries.""" - - from .test_transpiler_unitary_decompositions import random_unitary - # find the number of qubits for hardware circuit n_hardware_qubits = max(nqubits, middle_qubit + 1) @@ -79,8 +86,6 @@ def test_fix_connectivity_unitaries(nqubits, unitary_dim, depth, middle_qubit): transpiler = StarConnectivity(middle_qubit=middle_qubit) transpiled, hardware_qubits = transpiler(original) - # check that transpiled circuit can be executed - assert transpiler.is_satisfied(transpiled) # check that execution results agree with original (using simulation) backend = NumpyBackend() final_state = backend.execute_circuit(transpiled).state() diff --git a/tests/test_transpiler_unitary_decompositions.py b/tests/test_transpiler_unitary_decompositions.py index 47f46dc901..abe06bd9fd 100644 --- a/tests/test_transpiler_unitary_decompositions.py +++ b/tests/test_transpiler_unitary_decompositions.py @@ -18,7 +18,7 @@ two_qubit_decomposition, ) -NREPS = 5 # number of repetitions to execute random tests +NREPS = 3 # number of repetitions to execute random tests ATOL = 1e-12 @@ -187,3 +187,17 @@ def test_two_qubit_decomposition_bell_unitary(run_number, hz_zero): c.add(two_qubit_decomposition(0, 1, unitary)) final_matrix = c.unitary(backend) np.testing.assert_allclose(final_matrix, unitary, atol=ATOL) + + +# def test_two_qubit_decomposition_no_entanglement(gatename): +# """Test two-qubit decomposition on unitary that creates no entanglement.""" +# backend = NumpyBackend() +# matrix = np.array([[-1.,0.,0.,0.], +# [0.,-1.,0.,0.], +# [0.,0.,1.,0.], +# [0.,0.,0.,1.], +# ]) +# c = Circuit(2) +# c.add(two_qubit_decomposition(0, 1, matrix)) +# final_matrix = c.unitary(backend) +# np.testing.assert_allclose(final_matrix, matrix, atol=ATOL) From 702178ccfa4d9ae7df92a6c5cbc00e16d1d6c956 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Wed, 1 Nov 2023 11:47:44 +0400 Subject: [PATCH 15/55] improve coverage --- src/qibo/transpiler/unitary_decompositions.py | 1 + .../test_transpiler_unitary_decompositions.py | 29 ++++++++++--------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py index c4171debf0..b3a3d57caa 100644 --- a/src/qibo/transpiler/unitary_decompositions.py +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -223,6 +223,7 @@ def two_qubit_decomposition(q0, q1, unitary): hx, hy, hz = calculate_h_vector(ud_diag) if np.allclose([hx, hy, hz], [0, 0, 0]): + u4, v4, ud, u1, v1 = magic_decomposition(unitary) gatelist = [gates.Unitary(u4 @ u1, q0), gates.Unitary(v4 @ v1, q1)] elif np.allclose(hz, 0): gatelist = cnot_decomposition_light(q0, q1, hx, hy) diff --git a/tests/test_transpiler_unitary_decompositions.py b/tests/test_transpiler_unitary_decompositions.py index abe06bd9fd..cfbd08509f 100644 --- a/tests/test_transpiler_unitary_decompositions.py +++ b/tests/test_transpiler_unitary_decompositions.py @@ -160,7 +160,7 @@ def test_two_qubit_decomposition(run_number): np.testing.assert_allclose(final_matrix, unitary, atol=ATOL) -@pytest.mark.parametrize("gatename", ["CNOT", "CZ", "SWAP", "iSWAP", "fSim"]) +@pytest.mark.parametrize("gatename", ["CNOT", "CZ", "SWAP", "iSWAP", "fSim", "I"]) def test_two_qubit_decomposition_common_gates(gatename): """Test general two-qubit decomposition on some common gates.""" backend = NumpyBackend() @@ -189,15 +189,18 @@ def test_two_qubit_decomposition_bell_unitary(run_number, hz_zero): np.testing.assert_allclose(final_matrix, unitary, atol=ATOL) -# def test_two_qubit_decomposition_no_entanglement(gatename): -# """Test two-qubit decomposition on unitary that creates no entanglement.""" -# backend = NumpyBackend() -# matrix = np.array([[-1.,0.,0.,0.], -# [0.,-1.,0.,0.], -# [0.,0.,1.,0.], -# [0.,0.,0.,1.], -# ]) -# c = Circuit(2) -# c.add(two_qubit_decomposition(0, 1, matrix)) -# final_matrix = c.unitary(backend) -# np.testing.assert_allclose(final_matrix, matrix, atol=ATOL) +def test_two_qubit_decomposition_no_entanglement(): + """Test two-qubit decomposition on unitary that creates no entanglement.""" + backend = NumpyBackend() + matrix = np.array( + [ + [-1.0, 0.0, 0.0, 0.0], + [0.0, -1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + ] + ) + c = Circuit(2) + c.add(two_qubit_decomposition(0, 1, matrix)) + final_matrix = c.unitary(backend) + np.testing.assert_allclose(final_matrix, matrix, atol=ATOL) From aee8f58115949af3fcb0e86eb71743fb504e0da8 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Thu, 2 Nov 2023 16:02:15 +0400 Subject: [PATCH 16/55] correctons by renato --- src/qibo/transpiler/unitary_decompositions.py | 13 ++++--- src/qibo/transpiler/unroller.py | 2 +- .../test_transpiler_unitary_decompositions.py | 38 +++++++------------ tests/test_transpiler_unroller.py | 9 ++--- 4 files changed, 25 insertions(+), 37 deletions(-) diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py index b3a3d57caa..15c8271ca2 100644 --- a/src/qibo/transpiler/unitary_decompositions.py +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -2,6 +2,7 @@ from scipy.linalg import expm from qibo import gates, matrices +from qibo.backends import NumpyBackend from qibo.config import raise_error magic_basis = np.array( @@ -165,12 +166,12 @@ def calculate_h_vector(ud_diag): def cnot_decomposition(q0, q1, hx, hy, hz): """Performs decomposition (6) from arXiv:quant-ph/0307177.""" - u3 = -1j * (matrices.X + matrices.Z) / np.sqrt(2) + u3 = -1j * matrices.H # use corrected version from PRA paper (not arXiv) - u2 = -u3 @ expm(-1j * (hx - np.pi / 4) * matrices.X) + u2 = -u3 @ gates.RX(0, 2 * hx - np.pi / 2).matrix(NumpyBackend()) # add an extra exp(-i pi / 4) global phase to get exact match - v2 = expm(-1j * hz * matrices.Z) * np.exp(-1j * np.pi / 4) - v3 = expm(1j * hy * matrices.Z) + v2 = np.exp(-1j * np.pi / 4) * gates.RZ(0, 2 * hz).matrix(NumpyBackend()) + v3 = gates.RZ(0, -2 * hy).matrix(NumpyBackend()) w = (matrices.I - 1j * matrices.X) / np.sqrt(2) # change CNOT to CZ using Hadamard gates return [ @@ -190,8 +191,8 @@ def cnot_decomposition(q0, q1, hx, hy, hz): def cnot_decomposition_light(q0, q1, hx, hy): """Performs decomposition (24) from arXiv:quant-ph/0307177.""" w = (matrices.I - 1j * matrices.X) / np.sqrt(2) - u2 = expm(-1j * hx * matrices.X) - v2 = expm(1j * hy * matrices.Z) + u2 = gates.RX(0, 2 * hx).matrix(NumpyBackend()) + v2 = gates.RZ(0, -2 * hy).matrix(NumpyBackend()) # change CNOT to CZ using Hadamard gates return [ gates.Unitary(np.conj(w).T, q0), diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 6d8fb72a77..2151309f36 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -227,7 +227,7 @@ def __call__(self, gate): iswap_dec.add( gates.CNOT, [ - gates.U3(0, 7 * np.pi / 2, np.pi, 0), + gates.U3(0, 3 * np.pi / 2, np.pi, 0), gates.U3(1, np.pi / 2, -np.pi, -np.pi), gates.iSWAP(0, 1), gates.U3(0, np.pi, 0, np.pi), diff --git a/tests/test_transpiler_unitary_decompositions.py b/tests/test_transpiler_unitary_decompositions.py index cfbd08509f..a9a53384de 100644 --- a/tests/test_transpiler_unitary_decompositions.py +++ b/tests/test_transpiler_unitary_decompositions.py @@ -5,6 +5,8 @@ from qibo import gates, matrices from qibo.backends import NumpyBackend from qibo.models import Circuit +from qibo.quantum_info.metrics import purity +from qibo.quantum_info.random_ensembles import random_unitary from qibo.transpiler.unitary_decompositions import ( bell_basis, calculate_h_vector, @@ -22,17 +24,12 @@ ATOL = 1e-12 -def random_state(nqubits): - shape = 2**nqubits - psi = np.random.random(shape) + 1j * np.random.random(shape) - return psi / np.sqrt(np.sum(np.abs(psi) ** 2)) - - -def random_unitary(nqubits): - """Generates a random unitary matrix acting on nqubits.""" - shape = 2 * (2**nqubits,) - m = np.random.random(shape) + 1j * np.random.random(shape) - return expm(1j * (m + np.conj(m.T))) +# TODO: confront with quantum_info function +def purity(state): + """Calculates the purity of the partial trace of a two-qubit state.""" + mat = np.reshape(state, (2, 2)) + reduced_rho = np.dot(mat, np.conj(mat.T)) + return np.trace(np.dot(reduced_rho, reduced_rho)) def bell_unitary(hx, hy, hz): @@ -44,13 +41,6 @@ def bell_unitary(hx, hy, hz): return expm(-1j * ham) -def purity(state): - """Calculates the purity of the partial trace of a two-qubit state.""" - mat = np.reshape(state, (2, 2)) - reduced_rho = np.dot(mat, np.conj(mat.T)) - return np.trace(np.dot(reduced_rho, reduced_rho)) - - def assert_single_qubits(psi, ua, ub): """Assert UA, UB map the maximally entangled basis ``psi`` to the magic basis.""" uaub = np.kron(ua, ub) @@ -78,7 +68,7 @@ def test_u3_decomposition(): @pytest.mark.parametrize("run_number", range(NREPS)) def test_eigenbasis_entanglement(run_number): """Check that the eigenvectors of UT_U are maximally entangled.""" - unitary = random_unitary(2) + unitary = random_unitary(4) states, eigvals = calculate_psi(unitary) np.testing.assert_allclose(np.abs(eigvals), np.ones(4)) for state in states.T: @@ -88,7 +78,7 @@ def test_eigenbasis_entanglement(run_number): @pytest.mark.parametrize("run_number", range(NREPS)) def test_v_decomposition(run_number): """Check that V_A V_B |psi_k> = |phi_k> according to Lemma 1.""" - unitary = random_unitary(2) + unitary = random_unitary(4) psi, eigvals = calculate_psi(unitary) va, vb = calculate_single_qubit_unitaries(psi) assert_single_qubits(psi, va, vb) @@ -97,7 +87,7 @@ def test_v_decomposition(run_number): @pytest.mark.parametrize("run_number", range(NREPS)) def test_u_decomposition(run_number): r"""Check that U_A\dagger U_B\dagger |psi_k tilde> = |phi_k> according to Lemma 1.""" - unitary = random_unitary(2) + unitary = random_unitary(4) psi, eigvals = calculate_psi(unitary) psi_tilde = np.conj(np.sqrt(eigvals)) * np.dot(unitary, psi) ua_dagger, ub_dagger = calculate_single_qubit_unitaries(psi_tilde) @@ -107,7 +97,7 @@ def test_u_decomposition(run_number): @pytest.mark.parametrize("run_number", range(NREPS)) def test_ud_eigenvalues(run_number): """Check that U_d is diagonal in the Bell basis.""" - unitary = random_unitary(2) + unitary = random_unitary(4) ua, ub, ud, va, vb = magic_decomposition(unitary) unitary_recon = np.kron(ua, ub) @ ud @ np.kron(va, vb) @@ -121,7 +111,7 @@ def test_ud_eigenvalues(run_number): @pytest.mark.parametrize("run_number", range(NREPS)) def test_calculate_h_vector(run_number): - unitary = random_unitary(2) + unitary = random_unitary(4) ua, ub, ud, va, vb = magic_decomposition(unitary) ud_diag = to_bell_diagonal(ud) assert ud_diag is not None @@ -153,7 +143,7 @@ def test_cnot_decomposition_light(run_number): @pytest.mark.parametrize("run_number", range(NREPS)) def test_two_qubit_decomposition(run_number): backend = NumpyBackend() - unitary = random_unitary(2) + unitary = random_unitary(4) c = Circuit(2) c.add(two_qubit_decomposition(0, 1, unitary)) final_matrix = c.unitary(backend) diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index d76f5365f0..afc9d214df 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -4,6 +4,7 @@ from qibo import gates from qibo.backends import NumpyBackend from qibo.models import Circuit +from qibo.quantum_info.random_ensembles import random_unitary from qibo.transpiler.abstract import NativeType from qibo.transpiler.unroller import ( DecompositionError, @@ -134,9 +135,7 @@ def test_fSim_to_native(natives): [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], ) def test_GeneralizedfSim_to_native(natives): - from .test_transpiler_unitary_decompositions import random_unitary - - unitary = random_unitary(1) + unitary = random_unitary(2) gate = gates.GeneralizedfSim(0, 1, unitary, phi=0.1) assert_matrices_allclose(gate, natives) @@ -166,9 +165,7 @@ def test_TOFFOLI_to_native(natives): ) @pytest.mark.parametrize("nqubits", [1, 2]) def test_unitary_to_native(nqubits, natives): - from .test_transpiler_unitary_decompositions import random_unitary - - u = random_unitary(nqubits) + u = random_unitary(2**nqubits) # transform to SU(2^nqubits) form u = u / np.sqrt(np.linalg.det(u)) gate = gates.Unitary(u, *range(nqubits)) From 4eaaadab45994f4221c9a39a90e8d4e2a814d062 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Fri, 3 Nov 2023 11:01:19 +0400 Subject: [PATCH 17/55] fixed tests in star connectivity --- tests/test_transpiler_star_connectivity.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_transpiler_star_connectivity.py b/tests/test_transpiler_star_connectivity.py index ff0d177915..9420b35b56 100644 --- a/tests/test_transpiler_star_connectivity.py +++ b/tests/test_transpiler_star_connectivity.py @@ -6,12 +6,11 @@ from qibo import gates from qibo.backends import NumpyBackend from qibo.models import Circuit +from qibo.quantum_info.random_ensembles import random_unitary from qibo.transpiler.pipeline import transpose_qubits from qibo.transpiler.router import ConnectivityError from qibo.transpiler.star_connectivity import StarConnectivity -from .test_transpiler_unitary_decompositions import random_unitary - def generate_random_circuit(nqubits, depth, seed=None, middle_qubit=2): """Generate random circuits one-qubit rotations and CZ gates.""" @@ -82,7 +81,11 @@ def test_fix_connectivity_unitaries(nqubits, unitary_dim, depth, middle_qubit): pairs = list(itertools.combinations(range(n_hardware_qubits), unitary_dim)) for _ in range(depth): qubits = pairs[int(np.random.randint(len(pairs)))] - original.add(gates.Unitary(random_unitary(unitary_dim), *qubits)) + original.add( + gates.Unitary( + random_unitary(2**unitary_dim, backend=NumpyBackend()), *qubits + ) + ) transpiler = StarConnectivity(middle_qubit=middle_qubit) transpiled, hardware_qubits = transpiler(original) From 45810b150d42064a510d423ad1c7a7275a8fb36e Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 3 Nov 2023 12:07:40 +0400 Subject: [PATCH 18/55] fix test for GPUs --- tests/test_transpiler_blocks.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/test_transpiler_blocks.py b/tests/test_transpiler_blocks.py index 46a605ddb2..51000fa885 100644 --- a/tests/test_transpiler_blocks.py +++ b/tests/test_transpiler_blocks.py @@ -274,7 +274,7 @@ def test_block_decomposition_measurements(): assert len(blocks[2].gates) == 4 -def test_circuit_blocks(): +def test_circuit_blocks(backend): circ = Circuit(4) circ.add(gates.H(1)) circ.add(gates.H(0)) @@ -294,9 +294,13 @@ def test_circuit_blocks(): assert block.name == index reconstructed_circ = circuit_blocks.circuit() # Here we can't use assert gates_equality because the order of the gates is changed - np.testing.assert_allclose( - np.array(circ().state(), dtype=float), - np.array(reconstructed_circ().state(), dtype=float), + # np.testing.assert_allclose( + # np.array(circ().state(), dtype=float), + # np.array(reconstructed_circ().state(), dtype=float), + # ) + backend.assert_allclose( + backend.execute_circuit(circ).state(), + backend.execute_circuit(reconstructed_circ).state(), ) first_block = circuit_blocks.search_by_index(0) assert_gates_equality( From a2683556b3006eab4115eef4a8f7401186f54b1d Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 3 Nov 2023 08:29:11 +0000 Subject: [PATCH 19/55] Update src/qibo/transpiler/star_connectivity.py --- src/qibo/transpiler/star_connectivity.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/qibo/transpiler/star_connectivity.py b/src/qibo/transpiler/star_connectivity.py index 29b5d661da..019631f0cb 100644 --- a/src/qibo/transpiler/star_connectivity.py +++ b/src/qibo/transpiler/star_connectivity.py @@ -116,8 +116,7 @@ def __call__(self, circuit: Circuit, initial_layout=None): # add gate to the hardware circuit if isinstance(gate, gates.Unitary): # gates.Unitary requires matrix as first argument - backend = NumpyBackend() - matrix = gate.matrix(backend) + matrix = gate.init_args[0] new.add(gate.__class__(matrix, *qubits, **gate.init_kwargs)) else: new.add(gate.__class__(*qubits, **gate.init_kwargs)) From 98365424b382fc5ffc557816ea79087ba66905d0 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 3 Nov 2023 14:23:41 +0400 Subject: [PATCH 20/55] fix tests for GPU backends --- src/qibo/transpiler/unitary_decompositions.py | 4 +- .../test_transpiler_unitary_decompositions.py | 44 +++++---- tests/test_transpiler_unroller.py | 93 ++++++++++--------- 3 files changed, 75 insertions(+), 66 deletions(-) diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py index 15c8271ca2..eda0cfb5aa 100644 --- a/src/qibo/transpiler/unitary_decompositions.py +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -49,9 +49,9 @@ def calculate_psi(unitary): of UT_U. """ # write unitary in magic basis - u_magic = np.dot(np.dot(np.conj(magic_basis.T), unitary), magic_basis) + u_magic = np.transpose(np.conj(magic_basis)) @ unitary @ magic_basis # construct and diagonalize UT_U - ut_u = np.dot(u_magic.T, u_magic) + ut_u = np.transpose(u_magic) @ u_magic # When the matrix given to np.linalg.eig is a diagonal matrix up to machine precision the decomposition # is not accurate anymore. decimals = 20 works for random 2q Clifford unitaries. eigvals, psi_magic = np.linalg.eig(np.round(ut_u, decimals=20)) diff --git a/tests/test_transpiler_unitary_decompositions.py b/tests/test_transpiler_unitary_decompositions.py index a9a53384de..dcaa9f23b1 100644 --- a/tests/test_transpiler_unitary_decompositions.py +++ b/tests/test_transpiler_unitary_decompositions.py @@ -24,12 +24,13 @@ ATOL = 1e-12 -# TODO: confront with quantum_info function -def purity(state): - """Calculates the purity of the partial trace of a two-qubit state.""" - mat = np.reshape(state, (2, 2)) - reduced_rho = np.dot(mat, np.conj(mat.T)) - return np.trace(np.dot(reduced_rho, reduced_rho)) +# # TODO: confront with quantum_info function +# def purity(state, backend): +# """Calculates the purity of the partial trace of a two-qubit state.""" +# # mat = np.reshape(state, (2, 2)) +# # reduced_rho = np.dot(mat, np.conj(mat.T)) +# reduced_rho = backend.calculate_ +# return np.trace(np.dot(reduced_rho, reduced_rho)) def bell_unitary(hx, hy, hz): @@ -52,33 +53,38 @@ def assert_single_qubits(psi, ua, ub): # np.testing.assert_allclose(final_state, target_state, atol=1e-12) -def test_u3_decomposition(): - backend = NumpyBackend() +def test_u3_decomposition(backend): theta, phi, lam = 0.1, 0.2, 0.3 u3_matrix = gates.U3(0, theta, phi, lam).matrix(backend) + rz1 = gates.RZ(0, phi).matrix(backend) rz2 = gates.RZ(0, theta).matrix(backend) rz3 = gates.RZ(0, lam).matrix(backend) rx1 = gates.RX(0, -np.pi / 2).matrix(backend) rx2 = gates.RX(0, np.pi / 2).matrix(backend) + target_matrix = rz1 @ rx1 @ rz2 @ rx2 @ rz3 - np.testing.assert_allclose(u3_matrix, target_matrix) + + backend.assert_allclose(u3_matrix, target_matrix) +@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) @pytest.mark.parametrize("run_number", range(NREPS)) -def test_eigenbasis_entanglement(run_number): +def test_eigenbasis_entanglement(backend, run_number, seed): """Check that the eigenvectors of UT_U are maximally entangled.""" - unitary = random_unitary(4) + unitary = random_unitary(4, seed=seed, backend=backend) states, eigvals = calculate_psi(unitary) - np.testing.assert_allclose(np.abs(eigvals), np.ones(4)) - for state in states.T: - np.testing.assert_allclose(purity(state), 0.5) + eigvals = backend.cast(eigvals, dtype=eigvals.dtype) + backend.assert_allclose(np.abs(eigvals), np.ones(4)) + for state in np.transpose(states): + state = backend.partial_trace(state, [1], 2) + backend.assert_allclose(purity(state), 0.5) @pytest.mark.parametrize("run_number", range(NREPS)) def test_v_decomposition(run_number): """Check that V_A V_B |psi_k> = |phi_k> according to Lemma 1.""" - unitary = random_unitary(4) + unitary = random_unitary(4, seed=seed, backend=backend) psi, eigvals = calculate_psi(unitary) va, vb = calculate_single_qubit_unitaries(psi) assert_single_qubits(psi, va, vb) @@ -87,7 +93,7 @@ def test_v_decomposition(run_number): @pytest.mark.parametrize("run_number", range(NREPS)) def test_u_decomposition(run_number): r"""Check that U_A\dagger U_B\dagger |psi_k tilde> = |phi_k> according to Lemma 1.""" - unitary = random_unitary(4) + unitary = random_unitary(4, seed=seed, backend=backend) psi, eigvals = calculate_psi(unitary) psi_tilde = np.conj(np.sqrt(eigvals)) * np.dot(unitary, psi) ua_dagger, ub_dagger = calculate_single_qubit_unitaries(psi_tilde) @@ -97,7 +103,7 @@ def test_u_decomposition(run_number): @pytest.mark.parametrize("run_number", range(NREPS)) def test_ud_eigenvalues(run_number): """Check that U_d is diagonal in the Bell basis.""" - unitary = random_unitary(4) + unitary = random_unitary(4, seed=seed, backend=backend) ua, ub, ud, va, vb = magic_decomposition(unitary) unitary_recon = np.kron(ua, ub) @ ud @ np.kron(va, vb) @@ -111,7 +117,7 @@ def test_ud_eigenvalues(run_number): @pytest.mark.parametrize("run_number", range(NREPS)) def test_calculate_h_vector(run_number): - unitary = random_unitary(4) + unitary = random_unitary(4, seed=seed, backend=backend) ua, ub, ud, va, vb = magic_decomposition(unitary) ud_diag = to_bell_diagonal(ud) assert ud_diag is not None @@ -143,7 +149,7 @@ def test_cnot_decomposition_light(run_number): @pytest.mark.parametrize("run_number", range(NREPS)) def test_two_qubit_decomposition(run_number): backend = NumpyBackend() - unitary = random_unitary(4) + unitary = random_unitary(4, seed=seed, backend=backend) c = Circuit(2) c.add(two_qubit_decomposition(0, 1, unitary)) final_matrix = c.unitary(backend) diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index afc9d214df..736b2ad8ae 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -14,64 +14,65 @@ ) -def assert_matrices_allclose(gate, two_qubit_natives): - backend = NumpyBackend() - target_matrix = gate.matrix(backend) +def assert_matrices_allclose(gate, two_qubit_natives, backend): + target_unitary = gate.matrix(backend) # Remove global phase from target matrix - target_unitary = target_matrix / np.power( - np.linalg.det(target_matrix), 1 / float(target_matrix.shape[0]), dtype=complex + target_unitary /= np.linalg.det(target_unitary) ** ( + 1 / float(target_unitary.shape[0]) ) circuit = Circuit(len(gate.qubits)) circuit.add(translate_gate(gate, two_qubit_natives)) - native_matrix = circuit.unitary(backend) + native_unitary = circuit.unitary(backend) # Remove global phase from native matrix - native_unitary = native_matrix / np.power( - np.linalg.det(native_matrix), 1 / float(native_matrix.shape[0]), dtype=complex + native_unitary /= np.linalg.det(native_unitary) ** ( + 1 / float(native_unitary.shape[0]) ) + # There can still be phase differences of -1, -1j, 1j c = 0 for phase in [1, -1, 1j, -1j]: if np.allclose(phase * native_unitary, target_unitary, atol=1e-12): c = 1 - np.testing.assert_allclose(c, 1) + + backend.assert_allclose(c, 1) @pytest.mark.parametrize("gatename", ["H", "X", "Y", "I"]) -def test_pauli_to_native(gatename): +def test_pauli_to_native(backend, gatename): gate = getattr(gates, gatename)(0) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) @pytest.mark.parametrize("gatename", ["RX", "RY", "RZ"]) -def test_rotations_to_native(gatename): +def test_rotations_to_native(backend, gatename): gate = getattr(gates, gatename)(0, theta=0.1) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) @pytest.mark.parametrize("gatename", ["S", "SDG", "T", "TDG"]) -def test_special_single_qubit_to_native(gatename): +def test_special_single_qubit_to_native(backend, gatename): gate = getattr(gates, gatename)(0) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) -def test_u1_to_native(): +def test_u1_to_native(backend): gate = gates.U1(0, theta=0.5) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) -def test_u2_to_native(): +def test_u2_to_native(backend): gate = gates.U2(0, phi=0.1, lam=0.3) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) -def test_u3_to_native(): +def test_u3_to_native(backend): gate = gates.U3(0, theta=0.2, phi=0.1, lam=0.3) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) -def test_gpi2_to_native(): +def test_gpi2_to_native(backend): gate = gates.GPI2(0, phi=0.123) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) @pytest.mark.parametrize("gatename", ["CNOT", "CZ", "SWAP", "iSWAP", "FSWAP"]) @@ -79,9 +80,9 @@ def test_gpi2_to_native(): "natives", [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], ) -def test_two_qubit_to_native(gatename, natives): +def test_two_qubit_to_native(backend, gatename, natives): gate = getattr(gates, gatename)(0, 1) - assert_matrices_allclose(gate, natives) + assert_matrices_allclose(gate, natives, backend) @pytest.mark.parametrize( @@ -89,55 +90,56 @@ def test_two_qubit_to_native(gatename, natives): [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], ) @pytest.mark.parametrize("gatename", ["CRX", "CRY", "CRZ"]) -def test_controlled_rotations_to_native(gatename, natives): +def test_controlled_rotations_to_native(backend, gatename, natives): gate = getattr(gates, gatename)(0, 1, 0.3) - assert_matrices_allclose(gate, natives) + assert_matrices_allclose(gate, natives, backend) @pytest.mark.parametrize( "natives", [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], ) -def test_cu1_to_native(natives): +def test_cu1_to_native(backend, natives): gate = gates.CU1(0, 1, theta=0.4) - assert_matrices_allclose(gate, natives) + assert_matrices_allclose(gate, natives, backend) @pytest.mark.parametrize( "natives", [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], ) -def test_cu2_to_native(natives): +def test_cu2_to_native(backend, natives): gate = gates.CU2(0, 1, phi=0.2, lam=0.3) - assert_matrices_allclose(gate, natives) + assert_matrices_allclose(gate, natives, backend) @pytest.mark.parametrize( "natives", [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], ) -def test_cu3_to_native(natives): +def test_cu3_to_native(backend, natives): gate = gates.CU3(0, 1, theta=0.2, phi=0.3, lam=0.4) - assert_matrices_allclose(gate, natives) + assert_matrices_allclose(gate, natives, backend) @pytest.mark.parametrize( "natives", [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], ) -def test_fSim_to_native(natives): +def test_fSim_to_native(backend, natives): gate = gates.fSim(0, 1, theta=0.3, phi=0.1) - assert_matrices_allclose(gate, natives) + assert_matrices_allclose(gate, natives, backend) +@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) @pytest.mark.parametrize( "natives", [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], ) -def test_GeneralizedfSim_to_native(natives): - unitary = random_unitary(2) +def test_GeneralizedfSim_to_native(backend, natives, seed): + unitary = random_unitary(2, seed=seed, backend=backend) gate = gates.GeneralizedfSim(0, 1, unitary, phi=0.1) - assert_matrices_allclose(gate, natives) + assert_matrices_allclose(gate, natives, backend) @pytest.mark.parametrize( @@ -145,31 +147,32 @@ def test_GeneralizedfSim_to_native(natives): [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], ) @pytest.mark.parametrize("gatename", ["RXX", "RZZ", "RYY"]) -def test_rnn_to_native(gatename, natives): +def test_rnn_to_native(backend, gatename, natives): gate = getattr(gates, gatename)(0, 1, theta=0.1) - assert_matrices_allclose(gate, natives) + assert_matrices_allclose(gate, natives, backend) @pytest.mark.parametrize( "natives", [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], ) -def test_TOFFOLI_to_native(natives): +def test_TOFFOLI_to_native(backend, natives): gate = gates.TOFFOLI(0, 1, 2) - assert_matrices_allclose(gate, natives) + assert_matrices_allclose(gate, natives, backend) +@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) @pytest.mark.parametrize( "natives", [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], ) @pytest.mark.parametrize("nqubits", [1, 2]) -def test_unitary_to_native(nqubits, natives): - u = random_unitary(2**nqubits) +def test_unitary_to_native(backend, nqubits, natives, seed): + u = random_unitary(2**nqubits, seed=seed, backend=backend) # transform to SU(2^nqubits) form u = u / np.sqrt(np.linalg.det(u)) gate = gates.Unitary(u, *range(nqubits)) - assert_matrices_allclose(gate, natives) + assert_matrices_allclose(gate, natives, backend) def test_count_1q(): From 6aa6828065f982a94e72c981ef619b3180116b5b Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Fri, 3 Nov 2023 14:27:57 +0400 Subject: [PATCH 21/55] new features for Sabre transpiler --- src/qibo/transpiler/router.py | 42 ++++++++++++++++++++++----------- tests/test_transpiler_router.py | 2 +- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 3abae571c0..2f7ca24710 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -1,3 +1,4 @@ +import random from copy import deepcopy import matplotlib.pyplot as plt @@ -439,24 +440,37 @@ def circuit_to_physical(self, circuit_qubit: int): class Sabre(Router): - def __init__(self, connectivity: nx.Graph, lookahead: int = 2, decay: float = 0.6): + def __init__( + self, + connectivity: nx.Graph, + lookahead: int = 2, + decay_lookahead: float = 0.6, + delta=0.001, + seed=42, + ): """Routing algorithm proposed in https://doi.org/10.48550/arXiv.1809.02573 Args: connectivity (dict): hardware chip connectivity. lookahead (int): lookahead factor, how many dag layers will be considered in computing the cost. - decay (float): value in interval [0,1]. + decay_lookahead (float): value in interval [0,1]. How the weight of the distance in the dag layers decays in computing the cost. + delta (float): this parameter defines the number of swaps vs depth trade-off by deciding + how the algorithm tends to select non-overlapping SWAPs. + seed (int): seed for the candidate random choice as tiebraker. """ self.connectivity = connectivity self.lookahead = lookahead - self.decay = decay + self.decay = decay_lookahead + self.delta = delta + self._delta_register = None self._dist_matrix = None self._dag = None self._front_layer = None self.circuit = None self._memory_map = None + random.seed(seed) def __call__(self, circuit, initial_layout): """Route the circuit. @@ -484,6 +498,8 @@ def preprocessing(self, circuit: Circuit, initial_layout): - _dag: direct acyclic graph of the circuit based on commutativity. - _memory_map: list to remember previous SWAP moves. - _front_layer: list containing the blocks to be executed. + - _delta_register: list containing the special weigh added to qubits + to prevent overlapping swaps. """ self.circuit = CircuitMap(initial_layout, circuit) self._dist_matrix = nx.floyd_warshall_numpy(self.connectivity) @@ -491,6 +507,7 @@ def preprocessing(self, circuit: Circuit, initial_layout): self._memory_map = [] self.update_dag_layers() self.update_front_layer() + self._delta_register = [1.0 for _ in range(circuit.nqubits)] def update_dag_layers(self): for layer, nodes in enumerate(nx.topological_generations(self._dag)): @@ -517,13 +534,15 @@ def find_new_mapping(self): for candidate in self.swap_candidates(): candidates_evaluation[candidate] = self.compute_cost(candidate) best_candidate = min(candidates_evaluation, key=candidates_evaluation.get) + for qubit in self.circuit.logical_to_physical(best_candidate): + self._delta_register[qubit] += self.delta self.circuit.update(best_candidate) def compute_cost(self, candidate): """Compute the cost associated to a possible SWAP candidate.""" temporary_circuit = deepcopy(self.circuit) temporary_circuit.update(candidate) - if not self.check_new_mapping(temporary_circuit._circuit_logical): + if temporary_circuit._circuit_logical in self._memory_map: return float("inf") tot_distance = 0.0 weight = 1.0 @@ -533,20 +552,14 @@ def compute_cost(self, candidate): for gate in layer_gates: qubits = temporary_circuit.get_physical_qubits(gate) avg_layer_distance += ( - self._dist_matrix[qubits[0], qubits[1]] - 1.0 - ) / len(layer_gates) + max(self._delta_register[i] for i in qubits) + * (self._dist_matrix[qubits[0], qubits[1]] - 1.0) + / len(layer_gates) + ) tot_distance += weight * avg_layer_distance weight *= self.decay return tot_distance - def check_new_mapping(self, map): - """Check that the candidate will generate a new qubit mapping in order to avoid ending up in infinite cycles. - If the mapping is not new the cost associated to that candidate will be infinite. - """ - if map in self._memory_map: - return False - return True - def swap_candidates(self): """Return a list of possible candidate SWAPs (to be applied on logical qubits directly). The possible candidates are the ones sharing at least one qubit with a block in the front layer. @@ -600,6 +613,7 @@ def execute_blocks(self, blocklist: list): self.update_dag_layers() self.update_front_layer() self._memory_map = [] + self._delta_register = [1.0 for _ in self._delta_register] def draw_dag(dag: nx.DiGraph, filename=None): # pragma: no cover diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 0aff133dc4..2776e37651 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -306,7 +306,7 @@ def test_sabre_random_circuits(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=decay) + router = Sabre(connectivity=connectivity, lookahead=look, decay_lookahead=decay) circuit = generate_random_circuit(nqubits=5, ngates=gates) transpiled_circuit, final_qubit_map = router(circuit, initial_layout) assert router.added_swaps >= 0 From be6656249eb8e50da99299e260e273c16fba8622 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Fri, 3 Nov 2023 15:05:22 +0400 Subject: [PATCH 22/55] random choice and delta parameter in sabre --- src/qibo/transpiler/router.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 2f7ca24710..dec6a9c1cb 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -533,7 +533,11 @@ def find_new_mapping(self): self._memory_map.append(deepcopy(self.circuit._circuit_logical)) for candidate in self.swap_candidates(): candidates_evaluation[candidate] = self.compute_cost(candidate) - best_candidate = min(candidates_evaluation, key=candidates_evaluation.get) + best_cost = min(candidates_evaluation.values()) + best_candidates = [ + key for key, value in candidates_evaluation.items() if value == best_cost + ] + best_candidate = random.choice(best_candidates) for qubit in self.circuit.logical_to_physical(best_candidate): self._delta_register[qubit] += self.delta self.circuit.update(best_candidate) From cc5de3278fb2878a4b70965efbc6c9a6b87f976c Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 3 Nov 2023 15:48:17 +0400 Subject: [PATCH 23/55] making it compatible with all backends --- src/qibo/transpiler/unitary_decompositions.py | 57 ++++-- src/qibo/transpiler/unroller.py | 7 +- .../test_transpiler_unitary_decompositions.py | 182 ++++++++++-------- 3 files changed, 140 insertions(+), 106 deletions(-) diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py index eda0cfb5aa..5fe6b3a229 100644 --- a/src/qibo/transpiler/unitary_decompositions.py +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -2,7 +2,7 @@ from scipy.linalg import expm from qibo import gates, matrices -from qibo.backends import NumpyBackend +from qibo.backends import GlobalBackend, NumpyBackend from qibo.config import raise_error magic_basis = np.array( @@ -35,19 +35,25 @@ def u3_decomposition(unitary): return theta, phi, lam -def calculate_psi(unitary): - """Solves the eigenvalue problem of UT_U. +def calculate_psi(unitary, magic_basis=magic_basis, backend=None): + """Solves the eigenvalue problem of :math:`U^{T} U`. See step (1) of Appendix A in arXiv:quant-ph/0011050. Args: unitary (np.ndarray): Unitary matrix of the gate we are - decomposing in the computational basis. + decomposing in the computational basis. Returns: - Eigenvectors (in the computational basis) and eigenvalues - of UT_U. + Eigenvectors (in the computational basis) and eigenvalues of :math:`U^{T} U`. """ + if backend is None: # pragma: no cover + backend = GlobalBackend() + + if backend.__class__.__name__ == "CupyBackend": + raise_error(NotImplementedError, "CupyBackend does not support `linalg.eig.`") + + magic_basis = backend.cast(magic_basis, dtype=magic_basis.dtype) # write unitary in magic basis u_magic = np.transpose(np.conj(magic_basis)) @ unitary @ magic_basis # construct and diagonalize UT_U @@ -124,25 +130,34 @@ def calculate_diagonal(unitary, ua, ub, va, vb): ub *= det va *= det vb *= det - u_dagger = np.conj(np.kron(ua, ub).T) - v_dagger = np.conj(np.kron(va, vb).T) - ud = np.dot(np.dot(u_dagger, unitary), v_dagger) + u_dagger = np.transpose(np.conj(np.kron(ua, ub))) + v_dagger = np.transpose(np.conj(np.kron(va, vb))) + ud = u_dagger @ unitary @ v_dagger return ua, ub, ud, va, vb -def magic_decomposition(unitary): +def magic_decomposition(unitary, backend=None): + if backend is None: # pragma: no cover + backend = GlobalBackend() """Decomposes an arbitrary unitary to (A1) from arXiv:quant-ph/0011050.""" - psi, eigvals = calculate_psi(unitary) + psi, eigvals = calculate_psi(unitary, backend=backend) psi_tilde = np.conj(np.sqrt(eigvals)) * np.dot(unitary, psi) va, vb = calculate_single_qubit_unitaries(psi) ua_dagger, ub_dagger = calculate_single_qubit_unitaries(psi_tilde) - ua, ub = np.conj(ua_dagger.T), np.conj(ub_dagger.T) + ua, ub = np.transpose(np.conj(ua_dagger)), np.transpose(np.conj(ub_dagger)) return calculate_diagonal(unitary, ua, ub, va, vb) -def to_bell_diagonal(ud): +def to_bell_diagonal(ud, bell_basis=bell_basis, backend=None): """Transforms a matrix to the Bell basis and checks if it is diagonal.""" - ud_bell = np.dot(np.dot(np.conj(bell_basis).T, ud), bell_basis) + if backend is None: # pragma: no cover + backend = GlobalBackend() + + ud = backend.cast(ud, dtype=ud.dtype) + + bell_basis = backend.cast(bell_basis, dtype=bell_basis.dtype) + + ud_bell = np.transpose(np.conj(bell_basis)) @ ud @ bell_basis ud_diag = np.diag(ud_bell) if not np.allclose(np.diag(ud_diag), ud_bell): # pragma: no cover return None @@ -206,7 +221,7 @@ def cnot_decomposition_light(q0, q1, hx, hy): ] -def two_qubit_decomposition(q0, q1, unitary): +def two_qubit_decomposition(q0, q1, unitary, backend=None): """Performs two qubit unitary gate decomposition (24) from arXiv:quant-ph/0307177. Args: @@ -216,15 +231,19 @@ def two_qubit_decomposition(q0, q1, unitary): Returns: list of gates implementing decomposition (24) from arXiv:quant-ph/0307177 """ - ud_diag = to_bell_diagonal(unitary) + if backend is None: # pragma: no cover + backend = GlobalBackend() + + ud_diag = to_bell_diagonal(unitary, backend=backend) ud = None if ud_diag is None: - u4, v4, ud, u1, v1 = magic_decomposition(unitary) - ud_diag = to_bell_diagonal(ud) + u4, v4, ud, u1, v1 = magic_decomposition(unitary, backend=backend) + ud_diag = to_bell_diagonal(ud, backend=backend) hx, hy, hz = calculate_h_vector(ud_diag) + hx, hy, hz = float(hx), float(hy), float(hz) if np.allclose([hx, hy, hz], [0, 0, 0]): - u4, v4, ud, u1, v1 = magic_decomposition(unitary) + u4, v4, ud, u1, v1 = magic_decomposition(unitary, backend=backend) gatelist = [gates.Unitary(u4 @ u1, q0), gates.Unitary(v4 @ v1, q1)] elif np.allclose(hz, 0): gatelist = cnot_decomposition_light(q0, q1, hx, hy) diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 2151309f36..0fc6408daa 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -438,10 +438,13 @@ def __call__(self, gate): cz_dec.add( gates.Unitary, lambda gate: two_qubit_decomposition(0, 1, gate.parameters[0]) ) -cz_dec.add(gates.fSim, lambda gate: two_qubit_decomposition(0, 1, gate.matrix(backend))) +cz_dec.add( + gates.fSim, + lambda gate: two_qubit_decomposition(0, 1, gate.matrix(backend), backend=backend), +) cz_dec.add( gates.GeneralizedfSim, - lambda gate: two_qubit_decomposition(0, 1, gate.matrix(backend)), + lambda gate: two_qubit_decomposition(0, 1, gate.matrix(backend), backend=backend), ) diff --git a/tests/test_transpiler_unitary_decompositions.py b/tests/test_transpiler_unitary_decompositions.py index dcaa9f23b1..cb74761e15 100644 --- a/tests/test_transpiler_unitary_decompositions.py +++ b/tests/test_transpiler_unitary_decompositions.py @@ -2,9 +2,8 @@ import pytest from scipy.linalg import expm -from qibo import gates, matrices -from qibo.backends import NumpyBackend -from qibo.models import Circuit +from qibo import Circuit, gates, matrices +from qibo.config import PRECISION_TOL from qibo.quantum_info.metrics import purity from qibo.quantum_info.random_ensembles import random_unitary from qibo.transpiler.unitary_decompositions import ( @@ -20,18 +19,6 @@ two_qubit_decomposition, ) -NREPS = 3 # number of repetitions to execute random tests -ATOL = 1e-12 - - -# # TODO: confront with quantum_info function -# def purity(state, backend): -# """Calculates the purity of the partial trace of a two-qubit state.""" -# # mat = np.reshape(state, (2, 2)) -# # reduced_rho = np.dot(mat, np.conj(mat.T)) -# reduced_rho = backend.calculate_ -# return np.trace(np.dot(reduced_rho, reduced_rho)) - def bell_unitary(hx, hy, hz): ham = ( @@ -42,15 +29,14 @@ def bell_unitary(hx, hy, hz): return expm(-1j * ham) -def assert_single_qubits(psi, ua, ub): +def assert_single_qubits(backend, psi, ua, ub): """Assert UA, UB map the maximally entangled basis ``psi`` to the magic basis.""" uaub = np.kron(ua, ub) for i, j in zip(range(4), [0, 1, 3, 2]): final_state = np.dot(uaub, psi[:, i]) target_state = magic_basis[:, j] fidelity = np.abs(np.dot(np.conj(target_state), final_state)) - np.testing.assert_allclose(fidelity, 1) - # np.testing.assert_allclose(final_state, target_state, atol=1e-12) + backend.assert_allclose(fidelity, 1) def test_u3_decomposition(backend): @@ -69,112 +55,134 @@ def test_u3_decomposition(backend): @pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) -@pytest.mark.parametrize("run_number", range(NREPS)) -def test_eigenbasis_entanglement(backend, run_number, seed): - """Check that the eigenvectors of UT_U are maximally entangled.""" +def test_eigenbasis_entanglement(backend, seed): unitary = random_unitary(4, seed=seed, backend=backend) - states, eigvals = calculate_psi(unitary) - eigvals = backend.cast(eigvals, dtype=eigvals.dtype) - backend.assert_allclose(np.abs(eigvals), np.ones(4)) - for state in np.transpose(states): - state = backend.partial_trace(state, [1], 2) - backend.assert_allclose(purity(state), 0.5) + if backend.__class__.__name__ == "CupyBackend": + with pytest.raises(NotImplementedError): + calculate_psi(unitary, backend=backend) + else: + """Check that the eigenvectors of UT_U are maximally entangled.""" + states, eigvals = calculate_psi(unitary, backend=backend) + eigvals = backend.cast(eigvals, dtype=eigvals.dtype) + backend.assert_allclose(np.abs(eigvals), np.ones(4)) + for state in np.transpose(states): + state = backend.partial_trace(state, [1], 2) + backend.assert_allclose(purity(state), 0.5) -@pytest.mark.parametrize("run_number", range(NREPS)) -def test_v_decomposition(run_number): + +@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) +def test_v_decomposition(backend, seed): """Check that V_A V_B |psi_k> = |phi_k> according to Lemma 1.""" unitary = random_unitary(4, seed=seed, backend=backend) - psi, eigvals = calculate_psi(unitary) - va, vb = calculate_single_qubit_unitaries(psi) - assert_single_qubits(psi, va, vb) + if backend.__class__.__name__ == "CupyBackend": + with pytest.raises(NotImplementedError): + calculate_psi(unitary, backend=backend) + else: + psi, _ = calculate_psi(unitary, backend=backend) + va, vb = calculate_single_qubit_unitaries(psi) + assert_single_qubits(backend, psi, va, vb) -@pytest.mark.parametrize("run_number", range(NREPS)) -def test_u_decomposition(run_number): +@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) +def test_u_decomposition(backend, seed): r"""Check that U_A\dagger U_B\dagger |psi_k tilde> = |phi_k> according to Lemma 1.""" unitary = random_unitary(4, seed=seed, backend=backend) - psi, eigvals = calculate_psi(unitary) - psi_tilde = np.conj(np.sqrt(eigvals)) * np.dot(unitary, psi) - ua_dagger, ub_dagger = calculate_single_qubit_unitaries(psi_tilde) - assert_single_qubits(psi_tilde, ua_dagger, ub_dagger) + if backend.__class__.__name__ == "CupyBackend": + with pytest.raises(NotImplementedError): + calculate_psi(unitary, backend=backend) + else: + psi, eigvals = calculate_psi(unitary, backend=backend) + psi_tilde = np.conj(np.sqrt(eigvals)) * np.dot(unitary, psi) + ua_dagger, ub_dagger = calculate_single_qubit_unitaries(psi_tilde) + assert_single_qubits(backend, psi_tilde, ua_dagger, ub_dagger) -@pytest.mark.parametrize("run_number", range(NREPS)) -def test_ud_eigenvalues(run_number): +@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) +def test_ud_eigenvalues(backend, seed): """Check that U_d is diagonal in the Bell basis.""" unitary = random_unitary(4, seed=seed, backend=backend) - ua, ub, ud, va, vb = magic_decomposition(unitary) + if backend.__class__.__name__ == "CupyBackend": + with pytest.raises(NotImplementedError): + magic_decomposition(unitary, backend=backend) + else: + ua, ub, ud, va, vb = magic_decomposition(unitary, backend=backend) - unitary_recon = np.kron(ua, ub) @ ud @ np.kron(va, vb) - np.testing.assert_allclose(unitary_recon, unitary) + unitary_recon = np.kron(ua, ub) @ ud @ np.kron(va, vb) + backend.assert_allclose(unitary_recon, unitary) - ud_bell = np.dot(np.dot(np.conj(bell_basis).T, ud), bell_basis) - ud_diag = np.diag(ud_bell) - np.testing.assert_allclose(np.diag(ud_diag), ud_bell, atol=ATOL) - np.testing.assert_allclose(np.prod(ud_diag), 1) + ud_bell = np.transpose(np.conj(bell_basis)) @ ud @ bell_basis + ud_diag = np.diag(ud_bell) + backend.assert_allclose(np.diag(ud_diag), ud_bell, atol=PRECISION_TOL) + backend.assert_allclose(np.prod(ud_diag), 1) -@pytest.mark.parametrize("run_number", range(NREPS)) -def test_calculate_h_vector(run_number): +@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) +def test_calculate_h_vector(backend, seed): unitary = random_unitary(4, seed=seed, backend=backend) - ua, ub, ud, va, vb = magic_decomposition(unitary) - ud_diag = to_bell_diagonal(ud) - assert ud_diag is not None - hx, hy, hz = calculate_h_vector(ud_diag) - target_matrix = bell_unitary(hx, hy, hz) - np.testing.assert_allclose(ud, target_matrix, atol=ATOL) + if backend.__class__.__name__ == "CupyBackend": + with pytest.raises(NotImplementedError): + magic_decomposition(unitary, backend=backend) + else: + _, _, ud, _, _ = magic_decomposition(unitary, backend=backend) + ud_diag = to_bell_diagonal(ud, backend=backend) + assert ud_diag is not None + hx, hy, hz = calculate_h_vector(ud_diag) + target_matrix = bell_unitary(hx, hy, hz) + backend.assert_allclose(ud, target_matrix, atol=PRECISION_TOL) -@pytest.mark.parametrize("run_number", range(NREPS)) -def test_cnot_decomposition(run_number): +def test_cnot_decomposition(backend): hx, hy, hz = np.random.random(3) target_matrix = bell_unitary(hx, hy, hz) c = Circuit(2) c.add(cnot_decomposition(0, 1, hx, hy, hz)) - final_matrix = c.unitary(NumpyBackend()) - np.testing.assert_allclose(final_matrix, target_matrix, atol=ATOL) + final_matrix = c.unitary(backend) + backend.assert_allclose(final_matrix, target_matrix, atol=PRECISION_TOL) -@pytest.mark.parametrize("run_number", range(NREPS)) -def test_cnot_decomposition_light(run_number): +def test_cnot_decomposition_light(backend): hx, hy = np.random.random(2) target_matrix = bell_unitary(hx, hy, 0) c = Circuit(2) c.add(cnot_decomposition_light(0, 1, hx, hy)) - final_matrix = c.unitary(NumpyBackend()) - np.testing.assert_allclose(final_matrix, target_matrix, atol=ATOL) + final_matrix = c.unitary(backend) + backend.assert_allclose(final_matrix, target_matrix, atol=PRECISION_TOL) -@pytest.mark.parametrize("run_number", range(NREPS)) -def test_two_qubit_decomposition(run_number): - backend = NumpyBackend() +@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) +def test_two_qubit_decomposition(backend, seed): unitary = random_unitary(4, seed=seed, backend=backend) - c = Circuit(2) - c.add(two_qubit_decomposition(0, 1, unitary)) - final_matrix = c.unitary(backend) - np.testing.assert_allclose(final_matrix, unitary, atol=ATOL) + if backend.__class__.__name__ == "CupyBackend": + with pytest.raises(NotImplementedError): + two_qubit_decomposition(0, 1, unitary, backend=backend) + else: + c = Circuit(2) + c.add(two_qubit_decomposition(0, 1, unitary, backend=backend)) + final_matrix = c.unitary(backend) + backend.assert_allclose(final_matrix, unitary, atol=PRECISION_TOL) @pytest.mark.parametrize("gatename", ["CNOT", "CZ", "SWAP", "iSWAP", "fSim", "I"]) -def test_two_qubit_decomposition_common_gates(gatename): +def test_two_qubit_decomposition_common_gates(backend, gatename): """Test general two-qubit decomposition on some common gates.""" - backend = NumpyBackend() if gatename == "fSim": gate = gates.fSim(0, 1, theta=0.1, phi=0.2) else: gate = getattr(gates, gatename)(0, 1) matrix = gate.matrix(backend) - c = Circuit(2) - c.add(two_qubit_decomposition(0, 1, matrix)) - final_matrix = c.unitary(backend) - np.testing.assert_allclose(final_matrix, matrix, atol=ATOL) + if backend.__class__.__name__ == "CupyBackend" and gatename != "iSWAP": + with pytest.raises(NotImplementedError): + two_qubit_decomposition(0, 1, matrix, backend=backend) + else: + c = Circuit(2) + c.add(two_qubit_decomposition(0, 1, matrix, backend=backend)) + final_matrix = c.unitary(backend) + backend.assert_allclose(final_matrix, matrix, atol=PRECISION_TOL) -@pytest.mark.parametrize("run_number", range(NREPS)) @pytest.mark.parametrize("hz_zero", [False, True]) -def test_two_qubit_decomposition_bell_unitary(run_number, hz_zero): - backend = NumpyBackend() +def test_two_qubit_decomposition_bell_unitary(backend, hz_zero): hx, hy, hz = (2 * np.random.random(3) - 1) * np.pi if hz_zero: hz = 0 @@ -182,12 +190,11 @@ def test_two_qubit_decomposition_bell_unitary(run_number, hz_zero): c = Circuit(2) c.add(two_qubit_decomposition(0, 1, unitary)) final_matrix = c.unitary(backend) - np.testing.assert_allclose(final_matrix, unitary, atol=ATOL) + backend.assert_allclose(final_matrix, unitary, atol=PRECISION_TOL) -def test_two_qubit_decomposition_no_entanglement(): +def test_two_qubit_decomposition_no_entanglement(backend): """Test two-qubit decomposition on unitary that creates no entanglement.""" - backend = NumpyBackend() matrix = np.array( [ [-1.0, 0.0, 0.0, 0.0], @@ -196,7 +203,12 @@ def test_two_qubit_decomposition_no_entanglement(): [0.0, 0.0, 0.0, 1.0], ] ) + matrix = backend.cast(matrix, dtype=matrix.dtype) c = Circuit(2) - c.add(two_qubit_decomposition(0, 1, matrix)) - final_matrix = c.unitary(backend) - np.testing.assert_allclose(final_matrix, matrix, atol=ATOL) + if backend.__class__.__name__ == "CupyBackend": + with pytest.raises(NotImplementedError): + two_qubit_decomposition(0, 1, matrix, backend=backend) + else: + c.add(two_qubit_decomposition(0, 1, matrix, backend=backend)) + final_matrix = c.unitary(backend) + backend.assert_allclose(final_matrix, matrix, atol=PRECISION_TOL) From ddea483a8070c51b664e1675b29b28eed2a3c2cb Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Fri, 3 Nov 2023 16:00:21 +0400 Subject: [PATCH 24/55] add transpiler tutorial --- doc/source/code-examples/transpiler.rst | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 doc/source/code-examples/transpiler.rst diff --git a/doc/source/code-examples/transpiler.rst b/doc/source/code-examples/transpiler.rst new file mode 100644 index 0000000000..960656b42d --- /dev/null +++ b/doc/source/code-examples/transpiler.rst @@ -0,0 +1,29 @@ +.. _tutorials_transpiler: + +How to modify the transpiler? +============================= + +The transpiler is responsible for transforming a circuit to respect the chip connectivity and native gates. +The user can modify these attributes before executing a circuit. +Multiple transpilation steps can be implemented using the :class:`qibo.transpiler.pipeline.Pipeline`: +.. testcode:: python + + from qibo.transpiler.pipeline import Passes + from qibo.transpiler.abstract import NativeType + from qibo.transpiler.star_connectivity import StarConnectivity + from qibo.transpiler.unroller import NativeGates + + transpiler_pipeline = Passes( + [ + StarConnectivity(middle_qubit=2), + NativeGates(two_qubit_natives=NativeType.CZ), + ] + ) + +In this case circuits will first be transpiled to respect the 5-qubit star connectivity, with qubit 2 as the middle qubit. This will potentially add some SWAP gates. Then all gates will be converted to native. +The :class:`qibo.transpiler.unroller.NativeGates` transpiler used in this example assumes Z, RZ, GPI2 or U3 as the single-qubit native gates, and supports CZ and iSWAP as two-qubit natives. +In this case we restricted the two-qubit gate set to CZ only. +If the circuit to be executed contains gates that are not included in this gate set, they will be transformed to multiple gates from the gate set. +Arbitrary single-qubit gates are typically transformed to U3. +Arbitrary two-qubit gates are transformed to two or three CZ gates following their `universal CNOT decomposition `_. +The decomposition of some common gates such as the SWAP and CNOT is hard-coded for efficiency. From dad2eaeebb7952c295d7392357b2cdcbcac5e37a Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Fri, 3 Nov 2023 16:30:42 +0400 Subject: [PATCH 25/55] update tutorial --- doc/source/code-examples/transpiler.rst | 106 ++++++++++++++++++++---- 1 file changed, 90 insertions(+), 16 deletions(-) diff --git a/doc/source/code-examples/transpiler.rst b/doc/source/code-examples/transpiler.rst index 960656b42d..4f6ee1829d 100644 --- a/doc/source/code-examples/transpiler.rst +++ b/doc/source/code-examples/transpiler.rst @@ -3,27 +3,101 @@ How to modify the transpiler? ============================= -The transpiler is responsible for transforming a circuit to respect the chip connectivity and native gates. -The user can modify these attributes before executing a circuit. +Logical quantum circuits for quantum algorithms are hardware agnostic. Usually an all-to-all qubit connectivity +is assumed while most current hardware only allows the execution of two-qubit gates on a restricted subset of qubit +pairs. Moreover, quantum devices are restricted to executing a subset of gates, referred to as native. +This means that, in order to execute circuits on a real quantum chip, they must be transformed into an equivalent, +hardware specific, circuit. The transformation of the circuit is carried out by the transpiler through the resolution +of two key steps: connectivity matching and native gates decomposition. +In order to execute a gate between two qubits that are not directly connected SWAP gates are required. This procedure is called routing. +As on NISQ devices two-qubit gates are a large source of noise, this procedure generates an overall noisier circuit. +Therefore, the goal of an efficient routing algorithm is to minimize the number of SWAP gates introduced. +An important step to ease the connectivity problem, is finding anoptimal initial mapping between logical and physical qubits. +This step is called placement. +The native gates decomposition in the transpiling procedure is performed by the unroller. An optimal decomposition uses the least amount +of two-qubit native gates. It is also possible to reduce the number of gates of the resulting circuit by exploiting +commutation relations, KAK decomposition or machine learning techniques. +Qibo implements a built-in transpiler with customizable options for each step. The main algorithms that can +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. +- Reverse traversal: this technique uses one or more reverse routing passes to find an optimal mapping by +starting from a trivial layout. + +The routing problem can be solved with the following algorithms: +- Shortest paths: when unconnected logical qubits have to interact, they are moved on the chip on +the shortest path connecting them. When multiple shortest paths are present, the one that also matches +the largest number of the following two-qubit gates is chosen. +- Sabre: this heuristic routing technique uses a customizable cost function to add SWAP gates +that reduce the distance between unconnected qubits involved in two-qubit gates. + +Qibolab unroller applies recursively a set of hard-coded gates decompositions in order to translate any gate into +single and two-qubit native gates. Single qubit gates are translated into U3, RX, RZ, X and Z gates. It is possible to +fuse multiple single qubit gates acting on the same qubit into a single U3 gate. For the two-qubit native gates it +is possible to use CZ and/or iSWAP. When both CZ and iSWAP gates are available the chosen decomposition is the +one that minimizes the use of two-qubit gates. + Multiple transpilation steps can be implemented using the :class:`qibo.transpiler.pipeline.Pipeline`: .. testcode:: python - from qibo.transpiler.pipeline import Passes + import networkx as nx + + from qibo import Gates + from qibo.models import Circuit + from qibo.transpiler.pipeline import Passes, assert_transpiling from qibo.transpiler.abstract import NativeType - from qibo.transpiler.star_connectivity import StarConnectivity + from qibo.transpiler.optimizer import Preprocessing + from qibo.transpiler.router import ShortestPaths from qibo.transpiler.unroller import NativeGates + from qibo.transpiler.placer import Random + + # Define connectivity as nx.Graph + def star_connectivity(): + Q = [i for i in range(5)] + chip = nx.Graph() + chip.add_nodes_from(Q) + graph_list = [(Q[i], Q[2]) for i in range(5) if i != 2] + chip.add_edges_from(graph_list) + return chip + + # Define the circuit + circuit = Circuit(2) + circuit.add(gates.H(0)) + circuit.add(gates.CZ(0, 1)) + + # Define custom passes as a list + custom_passes = [] + # Preprocessing adds qubits in the original circuit to match the number of qubits in the chip + custom_passes.append(Preprocessing(connectivity=star_connectivity())) + # Placement step + custom_passes.append(Random(connectivity=star_connectivity())) + # Routing step + custom_passes.append(ShortestPaths(connectivity=star_connectivity())) + # Gate decomposition step + custom_passes.append(NativeGates(two_qubit_natives=NativeType.iSWAP)) + + # Define the general pipeline + custom_pipeline = Passes(custom_passes, connectivity=star_connectivity(), native_gates=NativeType.iSWAP) + + # Call the transpiler pipeline on the circuit + transpiled_circ, final_layout = custom_pipeline(circuit) - transpiler_pipeline = Passes( - [ - StarConnectivity(middle_qubit=2), - NativeGates(two_qubit_natives=NativeType.CZ), - ] + # Optinally call assert_transpiling to check that the final circuit can be executed on hardware + assert_transpiling( + original_circuit=circ, + transpiled_circuit=transpiled_circ, + connectivity=star_connectivity(), + initial_layout=initial_layout, + final_layout=final_layout, + native_gates=NativeType.iSWAP, ) -In this case circuits will first be transpiled to respect the 5-qubit star connectivity, with qubit 2 as the middle qubit. This will potentially add some SWAP gates. Then all gates will be converted to native. -The :class:`qibo.transpiler.unroller.NativeGates` transpiler used in this example assumes Z, RZ, GPI2 or U3 as the single-qubit native gates, and supports CZ and iSWAP as two-qubit natives. -In this case we restricted the two-qubit gate set to CZ only. -If the circuit to be executed contains gates that are not included in this gate set, they will be transformed to multiple gates from the gate set. -Arbitrary single-qubit gates are typically transformed to U3. -Arbitrary two-qubit gates are transformed to two or three CZ gates following their `universal CNOT decomposition `_. -The decomposition of some common gates such as the SWAP and CNOT is hard-coded for efficiency. +In this case circuits will first be transpiled to respect the 5-qubit star connectivity, with qubit 2 as the middle qubit. This will potentially add some SWAP gates. +Then all gates will be converted to native. The :class:`qibo.transpiler.unroller.NativeGates` transpiler used in this example assumes Z, RZ, GPI2 or U3 as +the single-qubit native gates, and supports CZ and iSWAP as two-qubit natives. In this case we restricted the two-qubit gate set to CZ only. +The final_layout contains the final logical-physical qubit mapping. From 683be3ed1df257056d51644f1528d72866143a85 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Fri, 3 Nov 2023 16:56:18 +0400 Subject: [PATCH 26/55] move transpiler exMPLE --- doc/source/code-examples/advancedexamples.rst | 105 ++++++++++++++++++ doc/source/code-examples/transpiler.rst | 103 ----------------- 2 files changed, 105 insertions(+), 103 deletions(-) delete mode 100644 doc/source/code-examples/transpiler.rst diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index c1ee4b61d8..c3378304ad 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -1956,3 +1956,108 @@ This can also be invoked directly from the ``result`` object: The expectation from samples currently works only for Hamiltonians that are diagonal in the computational basis. + + +.. _tutorials_transpiler: + +How to modify the transpiler? +----------------------------- + +Logical quantum circuits for quantum algorithms are hardware agnostic. Usually an all-to-all qubit connectivity +is assumed while most current hardware only allows the execution of two-qubit gates on a restricted subset of qubit +pairs. Moreover, quantum devices are restricted to executing a subset of gates, referred to as native. +This means that, in order to execute circuits on a real quantum chip, they must be transformed into an equivalent, +hardware specific, circuit. The transformation of the circuit is carried out by the transpiler through the resolution +of two key steps: connectivity matching and native gates decomposition. +In order to execute a gate between two qubits that are not directly connected SWAP gates are required. This procedure is called routing. +As on NISQ devices two-qubit gates are a large source of noise, this procedure generates an overall noisier circuit. +Therefore, the goal of an efficient routing algorithm is to minimize the number of SWAP gates introduced. +An important step to ease the connectivity problem, is finding anoptimal initial mapping between logical and physical qubits. +This step is called placement. +The native gates decomposition in the transpiling procedure is performed by the unroller. An optimal decomposition uses the least amount +of two-qubit native gates. It is also possible to reduce the number of gates of the resulting circuit by exploiting +commutation relations, KAK decomposition or machine learning techniques. +Qibo implements a built-in transpiler with customizable options for each step. The main algorithms that can +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. +- Reverse traversal: this technique uses one or more reverse routing passes to find an optimal mapping by +starting from a trivial layout. + +The routing problem can be solved with the following algorithms: +- Shortest paths: when unconnected logical qubits have to interact, they are moved on the chip on +the shortest path connecting them. When multiple shortest paths are present, the one that also matches +the largest number of the following two-qubit gates is chosen. +- Sabre: this heuristic routing technique uses a customizable cost function to add SWAP gates +that reduce the distance between unconnected qubits involved in two-qubit gates. + +Qibolab unroller applies recursively a set of hard-coded gates decompositions in order to translate any gate into +single and two-qubit native gates. Single qubit gates are translated into U3, RX, RZ, X and Z gates. It is possible to +fuse multiple single qubit gates acting on the same qubit into a single U3 gate. For the two-qubit native gates it +is possible to use CZ and/or iSWAP. When both CZ and iSWAP gates are available the chosen decomposition is the +one that minimizes the use of two-qubit gates. + +Multiple transpilation steps can be implemented using the :class:`qibo.transpiler.pipeline.Pipeline`: +.. testcode:: python + + import networkx as nx + + from qibo import Gates + from qibo.models import Circuit + from qibo.transpiler.pipeline import Passes, assert_transpiling + from qibo.transpiler.abstract import NativeType + from qibo.transpiler.optimizer import Preprocessing + from qibo.transpiler.router import ShortestPaths + from qibo.transpiler.unroller import NativeGates + from qibo.transpiler.placer import Random + + # Define connectivity as nx.Graph + def star_connectivity(): + Q = [i for i in range(5)] + chip = nx.Graph() + chip.add_nodes_from(Q) + graph_list = [(Q[i], Q[2]) for i in range(5) if i != 2] + chip.add_edges_from(graph_list) + return chip + + # Define the circuit + circuit = Circuit(2) + circuit.add(gates.H(0)) + circuit.add(gates.CZ(0, 1)) + + # Define custom passes as a list + custom_passes = [] + # Preprocessing adds qubits in the original circuit to match the number of qubits in the chip + custom_passes.append(Preprocessing(connectivity=star_connectivity())) + # Placement step + custom_passes.append(Random(connectivity=star_connectivity())) + # Routing step + custom_passes.append(ShortestPaths(connectivity=star_connectivity())) + # Gate decomposition step + custom_passes.append(NativeGates(two_qubit_natives=NativeType.iSWAP)) + + # Define the general pipeline + custom_pipeline = Passes(custom_passes, connectivity=star_connectivity(), native_gates=NativeType.iSWAP) + + # Call the transpiler pipeline on the circuit + transpiled_circ, final_layout = custom_pipeline(circuit) + + # Optinally call assert_transpiling to check that the final circuit can be executed on hardware + assert_transpiling( + original_circuit=circ, + transpiled_circuit=transpiled_circ, + connectivity=star_connectivity(), + initial_layout=initial_layout, + final_layout=final_layout, + native_gates=NativeType.iSWAP, + ) + +In this case circuits will first be transpiled to respect the 5-qubit star connectivity, with qubit 2 as the middle qubit. This will potentially add some SWAP gates. +Then all gates will be converted to native. The :class:`qibo.transpiler.unroller.NativeGates` transpiler used in this example assumes Z, RZ, GPI2 or U3 as +the single-qubit native gates, and supports CZ and iSWAP as two-qubit natives. In this case we restricted the two-qubit gate set to CZ only. +The final_layout contains the final logical-physical qubit mapping. diff --git a/doc/source/code-examples/transpiler.rst b/doc/source/code-examples/transpiler.rst deleted file mode 100644 index 4f6ee1829d..0000000000 --- a/doc/source/code-examples/transpiler.rst +++ /dev/null @@ -1,103 +0,0 @@ -.. _tutorials_transpiler: - -How to modify the transpiler? -============================= - -Logical quantum circuits for quantum algorithms are hardware agnostic. Usually an all-to-all qubit connectivity -is assumed while most current hardware only allows the execution of two-qubit gates on a restricted subset of qubit -pairs. Moreover, quantum devices are restricted to executing a subset of gates, referred to as native. -This means that, in order to execute circuits on a real quantum chip, they must be transformed into an equivalent, -hardware specific, circuit. The transformation of the circuit is carried out by the transpiler through the resolution -of two key steps: connectivity matching and native gates decomposition. -In order to execute a gate between two qubits that are not directly connected SWAP gates are required. This procedure is called routing. -As on NISQ devices two-qubit gates are a large source of noise, this procedure generates an overall noisier circuit. -Therefore, the goal of an efficient routing algorithm is to minimize the number of SWAP gates introduced. -An important step to ease the connectivity problem, is finding anoptimal initial mapping between logical and physical qubits. -This step is called placement. -The native gates decomposition in the transpiling procedure is performed by the unroller. An optimal decomposition uses the least amount -of two-qubit native gates. It is also possible to reduce the number of gates of the resulting circuit by exploiting -commutation relations, KAK decomposition or machine learning techniques. -Qibo implements a built-in transpiler with customizable options for each step. The main algorithms that can -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. -- Reverse traversal: this technique uses one or more reverse routing passes to find an optimal mapping by -starting from a trivial layout. - -The routing problem can be solved with the following algorithms: -- Shortest paths: when unconnected logical qubits have to interact, they are moved on the chip on -the shortest path connecting them. When multiple shortest paths are present, the one that also matches -the largest number of the following two-qubit gates is chosen. -- Sabre: this heuristic routing technique uses a customizable cost function to add SWAP gates -that reduce the distance between unconnected qubits involved in two-qubit gates. - -Qibolab unroller applies recursively a set of hard-coded gates decompositions in order to translate any gate into -single and two-qubit native gates. Single qubit gates are translated into U3, RX, RZ, X and Z gates. It is possible to -fuse multiple single qubit gates acting on the same qubit into a single U3 gate. For the two-qubit native gates it -is possible to use CZ and/or iSWAP. When both CZ and iSWAP gates are available the chosen decomposition is the -one that minimizes the use of two-qubit gates. - -Multiple transpilation steps can be implemented using the :class:`qibo.transpiler.pipeline.Pipeline`: -.. testcode:: python - - import networkx as nx - - from qibo import Gates - from qibo.models import Circuit - from qibo.transpiler.pipeline import Passes, assert_transpiling - from qibo.transpiler.abstract import NativeType - from qibo.transpiler.optimizer import Preprocessing - from qibo.transpiler.router import ShortestPaths - from qibo.transpiler.unroller import NativeGates - from qibo.transpiler.placer import Random - - # Define connectivity as nx.Graph - def star_connectivity(): - Q = [i for i in range(5)] - chip = nx.Graph() - chip.add_nodes_from(Q) - graph_list = [(Q[i], Q[2]) for i in range(5) if i != 2] - chip.add_edges_from(graph_list) - return chip - - # Define the circuit - circuit = Circuit(2) - circuit.add(gates.H(0)) - circuit.add(gates.CZ(0, 1)) - - # Define custom passes as a list - custom_passes = [] - # Preprocessing adds qubits in the original circuit to match the number of qubits in the chip - custom_passes.append(Preprocessing(connectivity=star_connectivity())) - # Placement step - custom_passes.append(Random(connectivity=star_connectivity())) - # Routing step - custom_passes.append(ShortestPaths(connectivity=star_connectivity())) - # Gate decomposition step - custom_passes.append(NativeGates(two_qubit_natives=NativeType.iSWAP)) - - # Define the general pipeline - custom_pipeline = Passes(custom_passes, connectivity=star_connectivity(), native_gates=NativeType.iSWAP) - - # Call the transpiler pipeline on the circuit - transpiled_circ, final_layout = custom_pipeline(circuit) - - # Optinally call assert_transpiling to check that the final circuit can be executed on hardware - assert_transpiling( - original_circuit=circ, - transpiled_circuit=transpiled_circ, - connectivity=star_connectivity(), - initial_layout=initial_layout, - final_layout=final_layout, - native_gates=NativeType.iSWAP, - ) - -In this case circuits will first be transpiled to respect the 5-qubit star connectivity, with qubit 2 as the middle qubit. This will potentially add some SWAP gates. -Then all gates will be converted to native. The :class:`qibo.transpiler.unroller.NativeGates` transpiler used in this example assumes Z, RZ, GPI2 or U3 as -the single-qubit native gates, and supports CZ and iSWAP as two-qubit natives. In this case we restricted the two-qubit gate set to CZ only. -The final_layout contains the final logical-physical qubit mapping. From faa7d45232358adf36e0e8996fc5f98ceb87e908 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 3 Nov 2023 19:29:26 +0400 Subject: [PATCH 27/55] `cupy` and `cuquantum` --- src/qibo/transpiler/unitary_decompositions.py | 6 +++--- tests/test_transpiler_unitary_decompositions.py | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py index 5fe6b3a229..2bbb7a1b28 100644 --- a/src/qibo/transpiler/unitary_decompositions.py +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -49,9 +49,9 @@ def calculate_psi(unitary, magic_basis=magic_basis, backend=None): """ if backend is None: # pragma: no cover backend = GlobalBackend() - - if backend.__class__.__name__ == "CupyBackend": - raise_error(NotImplementedError, "CupyBackend does not support `linalg.eig.`") + print(backend.__class__.__name__) + if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: + raise_error(NotImplementedError, f"{backend.__class__.__name__} does not support `linalg.eig.`") magic_basis = backend.cast(magic_basis, dtype=magic_basis.dtype) # write unitary in magic basis diff --git a/tests/test_transpiler_unitary_decompositions.py b/tests/test_transpiler_unitary_decompositions.py index cb74761e15..9c5d113ad3 100644 --- a/tests/test_transpiler_unitary_decompositions.py +++ b/tests/test_transpiler_unitary_decompositions.py @@ -58,7 +58,7 @@ def test_u3_decomposition(backend): def test_eigenbasis_entanglement(backend, seed): unitary = random_unitary(4, seed=seed, backend=backend) - if backend.__class__.__name__ == "CupyBackend": + if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: with pytest.raises(NotImplementedError): calculate_psi(unitary, backend=backend) else: @@ -75,7 +75,7 @@ def test_eigenbasis_entanglement(backend, seed): def test_v_decomposition(backend, seed): """Check that V_A V_B |psi_k> = |phi_k> according to Lemma 1.""" unitary = random_unitary(4, seed=seed, backend=backend) - if backend.__class__.__name__ == "CupyBackend": + if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: with pytest.raises(NotImplementedError): calculate_psi(unitary, backend=backend) else: @@ -88,7 +88,7 @@ def test_v_decomposition(backend, seed): def test_u_decomposition(backend, seed): r"""Check that U_A\dagger U_B\dagger |psi_k tilde> = |phi_k> according to Lemma 1.""" unitary = random_unitary(4, seed=seed, backend=backend) - if backend.__class__.__name__ == "CupyBackend": + if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: with pytest.raises(NotImplementedError): calculate_psi(unitary, backend=backend) else: @@ -102,7 +102,7 @@ def test_u_decomposition(backend, seed): def test_ud_eigenvalues(backend, seed): """Check that U_d is diagonal in the Bell basis.""" unitary = random_unitary(4, seed=seed, backend=backend) - if backend.__class__.__name__ == "CupyBackend": + if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: with pytest.raises(NotImplementedError): magic_decomposition(unitary, backend=backend) else: @@ -120,7 +120,7 @@ def test_ud_eigenvalues(backend, seed): @pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) def test_calculate_h_vector(backend, seed): unitary = random_unitary(4, seed=seed, backend=backend) - if backend.__class__.__name__ == "CupyBackend": + if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: with pytest.raises(NotImplementedError): magic_decomposition(unitary, backend=backend) else: @@ -153,7 +153,7 @@ def test_cnot_decomposition_light(backend): @pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) def test_two_qubit_decomposition(backend, seed): unitary = random_unitary(4, seed=seed, backend=backend) - if backend.__class__.__name__ == "CupyBackend": + if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: with pytest.raises(NotImplementedError): two_qubit_decomposition(0, 1, unitary, backend=backend) else: @@ -171,7 +171,7 @@ def test_two_qubit_decomposition_common_gates(backend, gatename): else: gate = getattr(gates, gatename)(0, 1) matrix = gate.matrix(backend) - if backend.__class__.__name__ == "CupyBackend" and gatename != "iSWAP": + if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"] and gatename != "iSWAP": with pytest.raises(NotImplementedError): two_qubit_decomposition(0, 1, matrix, backend=backend) else: @@ -205,7 +205,7 @@ def test_two_qubit_decomposition_no_entanglement(backend): ) matrix = backend.cast(matrix, dtype=matrix.dtype) c = Circuit(2) - if backend.__class__.__name__ == "CupyBackend": + if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: with pytest.raises(NotImplementedError): two_qubit_decomposition(0, 1, matrix, backend=backend) else: From 54a5a959e847e40f83e542975ae087314bb20798 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 3 Nov 2023 15:29:54 +0000 Subject: [PATCH 28/55] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/transpiler/unitary_decompositions.py | 5 ++++- tests/test_transpiler_unitary_decompositions.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py index 2bbb7a1b28..1b0455cd67 100644 --- a/src/qibo/transpiler/unitary_decompositions.py +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -51,7 +51,10 @@ def calculate_psi(unitary, magic_basis=magic_basis, backend=None): backend = GlobalBackend() print(backend.__class__.__name__) if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: - raise_error(NotImplementedError, f"{backend.__class__.__name__} does not support `linalg.eig.`") + raise_error( + NotImplementedError, + f"{backend.__class__.__name__} does not support `linalg.eig.`", + ) magic_basis = backend.cast(magic_basis, dtype=magic_basis.dtype) # write unitary in magic basis diff --git a/tests/test_transpiler_unitary_decompositions.py b/tests/test_transpiler_unitary_decompositions.py index 9c5d113ad3..9d1bde2f3e 100644 --- a/tests/test_transpiler_unitary_decompositions.py +++ b/tests/test_transpiler_unitary_decompositions.py @@ -171,7 +171,10 @@ def test_two_qubit_decomposition_common_gates(backend, gatename): else: gate = getattr(gates, gatename)(0, 1) matrix = gate.matrix(backend) - if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"] and gatename != "iSWAP": + if ( + backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"] + and gatename != "iSWAP" + ): with pytest.raises(NotImplementedError): two_qubit_decomposition(0, 1, matrix, backend=backend) else: From f7c3cdd9d548fb05dece38db7c494e54ebd1fb87 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 3 Nov 2023 19:57:48 +0400 Subject: [PATCH 29/55] more fixes to tests --- src/qibo/transpiler/unitary_decompositions.py | 2 +- src/qibo/transpiler/unroller.py | 2 +- tests/test_transpiler_unroller.py | 17 +++++++---------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py index 1b0455cd67..f20e874c39 100644 --- a/src/qibo/transpiler/unitary_decompositions.py +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -156,7 +156,7 @@ def to_bell_diagonal(ud, bell_basis=bell_basis, backend=None): if backend is None: # pragma: no cover backend = GlobalBackend() - ud = backend.cast(ud, dtype=ud.dtype) + ud = backend.cast(ud, dtype=complex) bell_basis = backend.cast(bell_basis, dtype=bell_basis.dtype) diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 0fc6408daa..d95a7a264a 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -436,7 +436,7 @@ def __call__(self, gate): ], ) cz_dec.add( - gates.Unitary, lambda gate: two_qubit_decomposition(0, 1, gate.parameters[0]) + gates.Unitary, lambda gate: two_qubit_decomposition(0, 1, gate.parameters[0], backend=backend) ) cz_dec.add( gates.fSim, diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index 736b2ad8ae..cee4007521 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -2,7 +2,6 @@ import pytest from qibo import gates -from qibo.backends import NumpyBackend from qibo.models import Circuit from qibo.quantum_info.random_ensembles import random_unitary from qibo.transpiler.abstract import NativeType @@ -15,26 +14,24 @@ def assert_matrices_allclose(gate, two_qubit_natives, backend): - target_unitary = gate.matrix(backend) + target_matrix = gate.matrix(backend) # Remove global phase from target matrix - target_unitary /= np.linalg.det(target_unitary) ** ( - 1 / float(target_unitary.shape[0]) + target_unitary = target_matrix / np.power( + np.linalg.det(target_matrix), 1 / float(target_matrix.shape[0]), dtype=complex ) circuit = Circuit(len(gate.qubits)) circuit.add(translate_gate(gate, two_qubit_natives)) - native_unitary = circuit.unitary(backend) + native_matrix = circuit.unitary(backend) # Remove global phase from native matrix - native_unitary /= np.linalg.det(native_unitary) ** ( - 1 / float(native_unitary.shape[0]) + native_unitary = native_matrix / np.power( + np.linalg.det(native_matrix), 1 / float(native_matrix.shape[0]), dtype=complex ) - # There can still be phase differences of -1, -1j, 1j c = 0 for phase in [1, -1, 1j, -1j]: if np.allclose(phase * native_unitary, target_unitary, atol=1e-12): c = 1 - - backend.assert_allclose(c, 1) + np.testing.assert_allclose(c, 1) @pytest.mark.parametrize("gatename", ["H", "X", "Y", "I"]) From c07b0f86259c37eb82dc5f2d99796a4f7c234488 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 3 Nov 2023 15:58:12 +0000 Subject: [PATCH 30/55] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/transpiler/unroller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index d95a7a264a..7e7d93e1ac 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -436,7 +436,8 @@ def __call__(self, gate): ], ) cz_dec.add( - gates.Unitary, lambda gate: two_qubit_decomposition(0, 1, gate.parameters[0], backend=backend) + gates.Unitary, + lambda gate: two_qubit_decomposition(0, 1, gate.parameters[0], backend=backend), ) cz_dec.add( gates.fSim, From aa8e311bb906e308e59b25ca39ce9775258f7fe9 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Sun, 5 Nov 2023 16:15:08 +0400 Subject: [PATCH 31/55] fix tests --- src/qibo/transpiler/unitary_decompositions.py | 5 ++--- tests/test_transpiler_unroller.py | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py index f20e874c39..cc589e12d5 100644 --- a/src/qibo/transpiler/unitary_decompositions.py +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -49,7 +49,7 @@ def calculate_psi(unitary, magic_basis=magic_basis, backend=None): """ if backend is None: # pragma: no cover backend = GlobalBackend() - print(backend.__class__.__name__) + if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: raise_error( NotImplementedError, @@ -156,8 +156,7 @@ def to_bell_diagonal(ud, bell_basis=bell_basis, backend=None): if backend is None: # pragma: no cover backend = GlobalBackend() - ud = backend.cast(ud, dtype=complex) - + ud = backend.cast(ud) bell_basis = backend.cast(bell_basis, dtype=bell_basis.dtype) ud_bell = np.transpose(np.conj(bell_basis)) @ ud @ bell_basis diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index cee4007521..15afe7aed8 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -2,6 +2,7 @@ import pytest from qibo import gates +from qibo.backends import NumpyBackend from qibo.models import Circuit from qibo.quantum_info.random_ensembles import random_unitary from qibo.transpiler.abstract import NativeType @@ -15,23 +16,30 @@ def assert_matrices_allclose(gate, two_qubit_natives, backend): target_matrix = gate.matrix(backend) + target_matrix = backend.cast(target_matrix, dtype=target_matrix.dtype) # Remove global phase from target matrix - target_unitary = target_matrix / np.power( + normalisation = np.power( np.linalg.det(target_matrix), 1 / float(target_matrix.shape[0]), dtype=complex ) + normalisation = backend.cast(normalisation, dtype=normalisation.dtype) + target_unitary = target_matrix / normalisation + circuit = Circuit(len(gate.qubits)) circuit.add(translate_gate(gate, two_qubit_natives)) native_matrix = circuit.unitary(backend) # Remove global phase from native matrix - native_unitary = native_matrix / np.power( + normalisation = np.power( np.linalg.det(native_matrix), 1 / float(native_matrix.shape[0]), dtype=complex ) + normalisation = backend.cast(normalisation, dtype=normalisation.dtype) + native_unitary = native_matrix / normalisation + # There can still be phase differences of -1, -1j, 1j c = 0 for phase in [1, -1, 1j, -1j]: if np.allclose(phase * native_unitary, target_unitary, atol=1e-12): c = 1 - np.testing.assert_allclose(c, 1) + backend.assert_allclose(c, 1) @pytest.mark.parametrize("gatename", ["H", "X", "Y", "I"]) @@ -165,7 +173,7 @@ def test_TOFFOLI_to_native(backend, natives): ) @pytest.mark.parametrize("nqubits", [1, 2]) def test_unitary_to_native(backend, nqubits, natives, seed): - u = random_unitary(2**nqubits, seed=seed, backend=backend) + u = random_unitary(2**nqubits, seed=seed, backend=NumpyBackend()) # transform to SU(2^nqubits) form u = u / np.sqrt(np.linalg.det(u)) gate = gates.Unitary(u, *range(nqubits)) From 1a1c343fe206ff7aabb64566320790ba7ca1010a Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Mon, 6 Nov 2023 08:52:19 +0400 Subject: [PATCH 32/55] fix coverage --- src/qibo/transpiler/unitary_decompositions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py index cc589e12d5..0ff1758ff1 100644 --- a/src/qibo/transpiler/unitary_decompositions.py +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -50,7 +50,10 @@ def calculate_psi(unitary, magic_basis=magic_basis, backend=None): if backend is None: # pragma: no cover backend = GlobalBackend() - if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: + if backend.__class__.__name__ in [ + "CupyBackend", + "CuQuantumBackend", + ]: # pragma: no cover raise_error( NotImplementedError, f"{backend.__class__.__name__} does not support `linalg.eig.`", From 6ce6e201633b43f0d0ca6878a9e42203c6f14c2b Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 7 Nov 2023 14:54:00 +0400 Subject: [PATCH 33/55] mask function to user and minor fixes --- src/qibo/transpiler/blocks.py | 116 ++++++++------ src/qibo/transpiler/pipeline.py | 6 +- src/qibo/transpiler/placer.py | 21 ++- src/qibo/transpiler/router.py | 186 ++++++++++------------- src/qibo/transpiler/star_connectivity.py | 42 ++--- 5 files changed, 184 insertions(+), 187 deletions(-) diff --git a/src/qibo/transpiler/blocks.py b/src/qibo/transpiler/blocks.py index bccdf25487..39b6101146 100644 --- a/src/qibo/transpiler/blocks.py +++ b/src/qibo/transpiler/blocks.py @@ -30,7 +30,7 @@ def __init__( @property def entangled(self): """True if the block contains two qubit gates.""" - return self.count_2q_gates() > 0 + return self._count_2q_gates() > 0 def rename(self, name): """Rename block""" @@ -46,9 +46,9 @@ def add_gate(self, gate: Gate): ) self.gates.append(gate) - def count_2q_gates(self): + def _count_2q_gates(self): """Return the number of two qubit gates in the block.""" - return count_2q_gates(self.gates) + return _count_2q_gates(self.gates) @property def qubits(self): @@ -63,11 +63,11 @@ def fuse(self, block: "Block", name: str = None): """Fuse the current block with a new one, the qubits they are acting on must coincide. Args: - block (:class:`qibolab.transpilers.blocks.Block`): block to fuse. + block (:class:`qibo.transpiler.blocks.Block`): block to fuse. name (str): name of the fused block. Return: - fused_block (:class:`qibolab.transpilers.blocks.Block`): fusion of the two input blocks. + fused_block (:class:`qibo.transpiler.blocks.Block`): fusion of the two input blocks. """ if not self.qubits == block.qubits: raise BlockingError( @@ -90,7 +90,7 @@ def commute(self, block: "Block"): """Check if a block commutes with the current one. Args: - block (:class:`qibolab.transpilers.blocks.Block`): block to check commutation. + block (:class:`qibo.transpiler.blocks.Block`): block to check commutation. Return: True if the two blocks don't share any qubit. @@ -165,26 +165,6 @@ def remove_block(self, block: "Block"): ) -def check_multi_qubit_measurements(circuit: Circuit): - """Return True if the block contains measurements acting on multiple qubits""" - for gate in circuit.queue: - if isinstance(gate, gates.M) and len(gate.qubits) > 1: - return True - return False - - -def split_multi_qubit_measurements(circuit: Circuit): - """Return an equivalent circuit containinig measurements acting only on single qubits""" - new_circuit = Circuit(circuit.nqubits) - for gate in circuit.queue: - if isinstance(gate, gates.M) and len(gate.qubits) > 1: - for qubit in gate.qubits: - new_circuit.add(gates.M(qubit)) - else: - new_circuit.add(gate) - return new_circuit - - def block_decomposition(circuit: Circuit, fuse: bool = True): """Decompose a circuit into blocks of gates acting on two qubits. Break measurements on multiple qubits into measurements of single qubit. @@ -200,9 +180,9 @@ def block_decomposition(circuit: Circuit, fuse: bool = True): raise BlockingError( "Only circuits with at least two qubits can be decomposed with block_decomposition." ) - if check_multi_qubit_measurements(circuit): - circuit = split_multi_qubit_measurements(circuit) - initial_blocks = initial_block_decomposition(circuit) + if _check_multi_qubit_measurements(circuit): + circuit = _split_multi_qubit_measurements(circuit) + initial_blocks = _initial_block_decomposition(circuit) if not fuse: return initial_blocks blocks = [] @@ -218,12 +198,14 @@ def block_decomposition(circuit: Circuit, fuse: bool = True): if not first_block.commute(second_block): break blocks.append(first_block) - remove_gates(initial_blocks, remove_list) + _remove_gates(initial_blocks, remove_list) return blocks -def initial_block_decomposition(circuit: Circuit): - """Decompose a circuit into blocks of gates acting on two qubits. +def _initial_block_decomposition(circuit: Circuit): + """Helper method for :meth:'qibo.transpiler.blocks.block_decomposition'. + + Decompose a circuit into blocks of gates acting on two qubits. This decomposition is not minimal. Args: @@ -234,7 +216,7 @@ def initial_block_decomposition(circuit: Circuit): """ blocks = [] all_gates = list(circuit.queue) - while count_multi_qubit_gates(all_gates) > 0: + while _count_multi_qubit_gates(all_gates) > 0: for idx, gate in enumerate(all_gates): if len(gate.qubits) == 2: qubits = gate.qubits @@ -242,7 +224,7 @@ def initial_block_decomposition(circuit: Circuit): block_gates.append(gate) block_gates.extend(_find_successive_gates(all_gates[idx + 1 :], qubits)) block = Block(qubits=qubits, gates=block_gates) - remove_gates(all_gates, block_gates) + _remove_gates(all_gates, block_gates) blocks.append(block) break elif len(gate.qubits) > 2: @@ -252,16 +234,16 @@ def initial_block_decomposition(circuit: Circuit): # Now we need to deal with the remaining spare single qubit gates while len(all_gates) > 0: first_qubit = all_gates[0].qubits[0] - block_gates = gates_on_qubit(gatelist=all_gates, qubit=first_qubit) - remove_gates(all_gates, block_gates) + block_gates = _gates_on_qubit(gatelist=all_gates, qubit=first_qubit) + _remove_gates(all_gates, block_gates) # Add other single qubits if there are still single qubit gates if len(all_gates) > 0: second_qubit = all_gates[0].qubits[0] - second_qubit_block_gates = gates_on_qubit( + second_qubit_block_gates = _gates_on_qubit( gatelist=all_gates, qubit=second_qubit ) block_gates += second_qubit_block_gates - remove_gates(all_gates, second_qubit_block_gates) + _remove_gates(all_gates, second_qubit_block_gates) block = Block(qubits=(first_qubit, second_qubit), gates=block_gates) # In case there are no other spare single qubit gates create a block using a following qubit as placeholder else: @@ -273,8 +255,35 @@ def initial_block_decomposition(circuit: Circuit): return blocks -def gates_on_qubit(gatelist, qubit): - """Return a list of all single qubit gates in gatelist acting on a specific qubit.""" +def _check_multi_qubit_measurements(circuit: Circuit): + """Helper method for :meth:'qibo.transpiler.blocks.block_decomposition'. + + Return True if the circuit contains measurements acting on multiple qubits.""" + for gate in circuit.queue: + if isinstance(gate, gates.M) and len(gate.qubits) > 1: + return True + return False + + +def _split_multi_qubit_measurements(circuit: Circuit): + """Helper method for :meth:'qibo.transpiler.blocks.block_decomposition'. + + Return an equivalent circuit containinig measurements acting only on single qubits. + """ + new_circuit = Circuit(circuit.nqubits) + for gate in circuit.queue: + if isinstance(gate, gates.M) and len(gate.qubits) > 1: + for qubit in gate.qubits: + new_circuit.add(gates.M(qubit)) + else: + new_circuit.add(gate) + return new_circuit + + +def _gates_on_qubit(gatelist, qubit): + """Helper method for :meth:'qibo.transpiler.blocks.block_decomposition'. + + Return a list of all single qubit gates in gatelist acting on a specific qubit.""" selected_gates = [] for gate in gatelist: if gate.qubits[0] == qubit: @@ -282,24 +291,33 @@ def gates_on_qubit(gatelist, qubit): return selected_gates -def remove_gates(gatelist, remove_list): - """Remove all gates present in remove_list from gatelist.""" +def _remove_gates(gatelist, remove_list): + """Helper method for :meth:'qibo.transpiler.blocks.block_decomposition'. + + Remove all gates present in remove_list from gatelist.""" for gate in remove_list: gatelist.remove(gate) -def count_2q_gates(gatelist: list): - """Return the number of two qubit gates in a list of gates.""" +def _count_2q_gates(gatelist: list): + """Helper method for :meth:'qibo.transpiler.blocks.block_decomposition'. + + Return the number of two qubit gates in a list of gates.""" return len([gate for gate in gatelist if len(gate.qubits) == 2]) -def count_multi_qubit_gates(gatelist: list): - """Return the number of multi qubit gates in a list of gates.""" +def _count_multi_qubit_gates(gatelist: list): + """Helper method for :meth:'qibo.transpiler.blocks.block_decomposition'. + + Return the number of multi qubit gates in a list of gates.""" return len([gate for gate in gatelist if len(gate.qubits) >= 2]) def _find_successive_gates(gates_list: list, qubits: tuple): - """Return a list containing all gates acting on qubits until a new two qubit gate acting on qubits is found.""" + """Helper method for :meth:'qibo.transpiler.blocks.block_decomposition'. + + Return a list containing all gates acting on qubits until a new two qubit gate acting on qubits is found. + """ successive_gates = [] for qubit in qubits: for gate in gates_list: @@ -311,7 +329,9 @@ def _find_successive_gates(gates_list: list, qubits: tuple): def _find_previous_gates(gates_list: list, qubits: tuple): - """Return a list containing all gates acting on qubits.""" + """Helper method for :meth:'qibo.transpiler.blocks.block_decomposition'. + + Return a list containing all gates acting on qubits.""" previous_gates = [] for gate in gates_list: if gate.qubits[0] in qubits: diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index 56a71e96bb..96a7459a27 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -54,7 +54,7 @@ def assert_circuit_equivalence( reordered_test_states = [] initial_map = np.array(list(initial_map.values())) reordered_test_states = [ - transpose_qubits(initial_state, initial_map) + _transpose_qubits(initial_state, initial_map) for initial_state in test_states ] else: @@ -67,7 +67,7 @@ def assert_circuit_equivalence( final_state = backend.execute_circuit( transpiled_circuit, initial_state=reordered_test_states[i] ).state() - final_state = transpose_qubits(final_state, ordering) + 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) @@ -75,7 +75,7 @@ def assert_circuit_equivalence( raise TranspilerPipelineError("Circuit equivalence not satisfied.") -def transpose_qubits(state: np.ndarray, qubits_ordering: np.ndarray): +def _transpose_qubits(state: np.ndarray, qubits_ordering: np.ndarray): """Reorders qubits of a given state vector. Args: diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 9f59c833c7..822d24f2c4 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -13,7 +13,7 @@ class PlacementError(Exception): def assert_placement(circuit: Circuit, layout: dict) -> bool: - """Check if layout is correct and matches the number of qubits of the circuit. + """Check if layout is in the correct form and matches the number of qubits of the circuit. Args: circuit (qibo.models.Circuit): Circuit model to check. @@ -35,7 +35,7 @@ def assert_placement(circuit: Circuit, layout: dict) -> bool: def assert_mapping_consistency(layout): - """Check if layout is correct. + """Check if layout is in the correct form. Args: layout (dict): physical to logical qubit mapping. @@ -208,11 +208,11 @@ def __call__(self, circuit): final_mapping = { "q" + str(i): final_mapping[i] for i in range(len(final_mapping)) } - final_cost = self.cost(final_graph, gates_qubits_pairs) + final_cost = self._cost(final_graph, gates_qubits_pairs) for _ in range(self.samples): mapping = dict(zip(keys, random.sample(range(nodes), nodes))) graph = nx.relabel_nodes(self.connectivity, mapping) - cost = self.cost(graph, gates_qubits_pairs) + cost = self._cost(graph, gates_qubits_pairs) if cost == 0: return {"q" + str(i): mapping[i] for i in range(len(mapping))} if cost < final_cost: @@ -221,8 +221,7 @@ def __call__(self, circuit): final_cost = cost return final_mapping - @staticmethod - def cost(graph, gates_qubits_pairs): + def _cost(graph, gates_qubits_pairs): """ Compute the cost associated to an initial layout as the lengh of the reduced circuit. @@ -246,7 +245,7 @@ class ReverseTraversal(Placer): Attributes: connectivity (networkx.Graph): chip connectivity. - routing_algorithm (qibolab.transpilers.routing.Transpiler): routing algorithm. + routing_algorithm (qibo.transpiler.abstract.Router): routing algorithm. depth (int): number of two qubit gates considered before finding initial layout. if 'None' just one backward step will be implemented. If depth is greater than the number of two qubit gates in the circuit, the circuit will be routed more than once. @@ -271,11 +270,11 @@ def __call__(self, circuit: Circuit): initial_placer = Trivial(self.connectivity) initial_placement = initial_placer(circuit=circuit) - new_circuit = self.assemble_circuit(circuit) - final_placement = self.routing_step(initial_placement, new_circuit) + new_circuit = self._assemble_circuit(circuit) + final_placement = self._routing_step(initial_placement, new_circuit) return final_placement - def assemble_circuit(self, circuit: Circuit): + def _assemble_circuit(self, circuit: Circuit): """Assemble a single circuit to apply Reverse Traversal placement based on depth. Example: for a circuit with four two qubit gates A-B-C-D using depth = 6, the function will return the circuit C-D-D-C-B-A. @@ -305,7 +304,7 @@ def assemble_circuit(self, circuit: Circuit): 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, layout: dict, circuit: Circuit): """Perform routing of the circuit. Args: diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index dec6a9c1cb..284750a9ea 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -88,56 +88,35 @@ def __call__(self, circuit: Circuit, initial_layout): """ self._mapping = initial_layout init_qubit_map = np.asarray(list(initial_layout.values())) - self.initial_checks(circuit.nqubits) + self._initial_checks(circuit.nqubits) self._gates_qubits_pairs = find_gates_qubits_pairs(circuit) self._mapping = dict(zip(range(len(initial_layout)), initial_layout.values())) self._graph = nx.relabel_nodes(self.connectivity, self._mapping) self._qubit_map = np.sort(init_qubit_map) self._swap_map = deepcopy(init_qubit_map) - self.first_transpiler_step(circuit) + self._first_transpiler_step(circuit) while len(self._gates_qubits_pairs) != 0: - self.transpiler_step(circuit) - hardware_mapped_circuit = self.remap_circuit( - np.argsort(init_qubit_map), original_circuit=circuit - ) + self._transpiler_step(circuit) + hardware_mapped_circuit = self._remap_circuit(np.argsort(init_qubit_map)) final_mapping = { "q" + str(j): self._swap_map[j] for j in range(self._graph.number_of_nodes()) } return hardware_mapped_circuit, final_mapping - def transpiler_step(self, qibo_circuit): - """Transpilation step. Find new mapping, add swap gates and apply gates that can be run with this configuration. - - Args: - qibo_circuit (qibo.models.Circuit): circuit to be transpiled. - """ - len_before_step = len(self._gates_qubits_pairs) - path, meeting_point = self.relocate() - self.add_swaps(path, meeting_point) - self.update_qubit_map() - self.add_gates(qibo_circuit, len_before_step - len(self._gates_qubits_pairs)) - - def first_transpiler_step(self, qibo_circuit): - """First transpilation step. Apply gates that can be run with the initial qubit mapping. - - Args: - qibo_circuit (qibo.models.Circuit): circuit to be transpiled. - """ - self._circuit_position = 0 - self._added_swaps = 0 - self._added_swaps_list = [] - len_2q_circuit = len(self._gates_qubits_pairs) - self._gates_qubits_pairs = self.reduce(self._graph) - self.add_gates(qibo_circuit, len_2q_circuit - len(self._gates_qubits_pairs)) + @property + def added_swaps(self): + """Number of added swaps during transpiling.""" + return self._added_swaps @property def sampling_split(self): + """Fraction of possible shortest paths to be analyzed.""" return self._sampling_split @sampling_split.setter def sampling_split(self, sampling_split): - """Set the sampling split. + """Set the sampling split, the fraction of possible shortest paths to be analyzed. Args: sampling_split (float): define fraction of shortest path tested. @@ -148,7 +127,32 @@ def sampling_split(self, sampling_split): else: raise_error(ValueError, "Sampling_split must be in (0:1].") - def reduce(self, graph): + def _transpiler_step(self, qibo_circuit): + """Transpilation step. Find new mapping, add swap gates and apply gates that can be run with this configuration. + + Args: + qibo_circuit (qibo.models.Circuit): circuit to be transpiled. + """ + len_before_step = len(self._gates_qubits_pairs) + path, meeting_point = self._relocate() + self._add_swaps(path, meeting_point) + self._update_qubit_map() + self._add_gates(qibo_circuit, len_before_step - len(self._gates_qubits_pairs)) + + def _first_transpiler_step(self, qibo_circuit): + """First transpilation step. Apply gates that can be run with the initial qubit mapping. + + Args: + qibo_circuit (qibo.models.Circuit): circuit to be transpiled. + """ + self._circuit_position = 0 + self._added_swaps = 0 + self._added_swaps_list = [] + len_2q_circuit = len(self._gates_qubits_pairs) + self._gates_qubits_pairs = self._reduce(self._graph) + self._add_gates(qibo_circuit, len_2q_circuit - len(self._gates_qubits_pairs)) + + def _reduce(self, graph): """Reduce the circuit, delete a 2-qubit gate if it can be applied on the current configuration. Args: @@ -165,7 +169,7 @@ def reduce(self, graph): del new_circuit[0] return new_circuit - def map_list(self, path): + def _map_list(self, path): """Return all possible walks of qubits, or a fraction, for a given path. Args: @@ -193,7 +197,7 @@ def map_list(self, path): meeting_point_list.append(i) return mapping_list, meeting_point_list - def relocate(self): + def _relocate(self): """A small greedy algorithm to decide which path to take, and how qubits should walk. Returns: @@ -201,7 +205,7 @@ def relocate(self): meeting_point (int): qubit meeting point in the path. """ nodes = self._graph.number_of_nodes() - circuit = self.reduce(self._graph) + circuit = self._reduce(self._graph) final_circuit = circuit keys = list(range(nodes)) final_graph = self._graph @@ -217,10 +221,10 @@ def relocate(self): # Here test all paths for path in path_list: # map_list uses self.sampling_split - list_, meeting_point_list = self.map_list(path) + list_, meeting_point_list = self._map_list(path) for j, mapping in enumerate(list_): new_graph = nx.relabel_nodes(self._graph, mapping) - new_circuit = self.reduce(new_graph) + new_circuit = self._reduce(new_graph) # Greedy looking for the optimal path and the optimal walk on this path if len(new_circuit) < len(final_circuit): final_graph = new_graph @@ -233,7 +237,7 @@ def relocate(self): self._gates_qubits_pairs = final_circuit return final_path, meeting_point - def initial_checks(self, qubits): + def _initial_checks(self, qubits): """Initialize the transpiled circuit and check if it can be mapped to the defined connectivity. Args: @@ -256,7 +260,7 @@ def initial_checks(self, qubits): assert_placement(new_circuit, self._mapping) self._transpiled_circuit = new_circuit - def add_gates(self, qibo_circuit: Circuit, matched_gates): + def _add_gates(self, qibo_circuit: Circuit, matched_gates): """Add one and two qubit gates to transpiled circuit until connectivity is matched. Args: @@ -296,7 +300,7 @@ def add_gates(self, qibo_circuit: Circuit, matched_gates): ) self._circuit_position += 1 - def add_swaps(self, path, meeting_point): + def _add_swaps(self, path, meeting_point): """Add swaps to the transpiled circuit to move qubits. Args: @@ -317,24 +321,19 @@ def add_swaps(self, path, meeting_point): self._transpiled_circuit.add(gate) self._added_swaps_list.append(gate) - def update_swap_map(self, swap: tuple): + def _update_swap_map(self, swap: tuple): """Update the qubit swap map.""" temp = self._swap_map[swap[0]] self._swap_map[swap[0]] = self._swap_map[swap[1]] self._swap_map[swap[1]] = temp - def update_qubit_map(self): + def _update_qubit_map(self): """Update the qubit mapping after adding swaps.""" old_mapping = self._qubit_map.copy() for key, value in self._mapping.items(): self._qubit_map[value] = old_mapping[key] - @property - def added_swaps(self): - """Number of added swaps during transpiling.""" - return self._added_swaps - - def remap_circuit(self, qubit_map, original_circuit: Circuit): + def _remap_circuit(self, qubit_map): """Map logical to physical qubits in a circuit. Args: @@ -348,7 +347,9 @@ def remap_circuit(self, qubit_map, original_circuit: Circuit): for gate in self._transpiled_circuit.queue: new_circuit.add(gate.on_qubits({q: qubit_map[q] for q in gate.qubits})) if gate in self._added_swaps_list: - self.update_swap_map(tuple(qubit_map[gate.qubits[i]] for i in range(2))) + self._update_swap_map( + tuple(qubit_map[gate.qubits[i]] for i in range(2)) + ) return new_circuit @@ -436,9 +437,6 @@ def circuit_to_physical(self, circuit_qubit: int): return self._physical_logical.index(self._circuit_logical[circuit_qubit]) -MAX_ITER = 10000 - - class Sabre(Router): def __init__( self, @@ -482,16 +480,21 @@ def __call__(self, circuit, initial_layout): Returns: (qibo.models.Circuit): routed circuit. """ - self.preprocessing(circuit=circuit, initial_layout=initial_layout) + self._preprocessing(circuit=circuit, initial_layout=initial_layout) while self._dag.number_of_nodes() != 0: - execute_block_list = self.check_execution() + execute_block_list = self._check_execution() if execute_block_list is not None: - self.execute_blocks(execute_block_list) + self._execute_blocks(execute_block_list) else: - self.find_new_mapping() + self._find_new_mapping() return self.circuit.routed_circuit(), self.circuit.final_layout() - def preprocessing(self, circuit: Circuit, initial_layout): + @property + def added_swaps(self): + """Number of SWAP gates added to the circuit during routing.""" + return self.circuit._swaps + + def _preprocessing(self, circuit: Circuit, initial_layout): """The following objects will be initialised: - circuit: class to represent circuit and to perform logical-physical qubit mapping. - _dist_matrix: matrix reporting the shortest path lengh between all node pairs. @@ -503,36 +506,31 @@ def preprocessing(self, circuit: Circuit, initial_layout): """ self.circuit = CircuitMap(initial_layout, circuit) self._dist_matrix = nx.floyd_warshall_numpy(self.connectivity) - self._dag = create_dag(self.circuit.blocks_qubits_pairs()) + self._dag = _create_dag(self.circuit.blocks_qubits_pairs()) self._memory_map = [] - self.update_dag_layers() - self.update_front_layer() + self._update_dag_layers() + self._update_front_layer() self._delta_register = [1.0 for _ in range(circuit.nqubits)] - def update_dag_layers(self): + def _update_dag_layers(self): for layer, nodes in enumerate(nx.topological_generations(self._dag)): for node in nodes: self._dag.nodes[node]["layer"] = layer - def update_front_layer(self): + def _update_front_layer(self): """Update the front layer of the dag.""" - self._front_layer = self.get_dag_layer(0) + self._front_layer = self._get_dag_layer(0) - def get_dag_layer(self, n_layer): + def _get_dag_layer(self, n_layer): """Return the n topological layer of the dag.""" return [node[0] for node in self._dag.nodes(data="layer") if node[1] == n_layer] - @property - def added_swaps(self): - """Number of SWAP gates added to the circuit during routing""" - return self.circuit._swaps - - def find_new_mapping(self): + def _find_new_mapping(self): """Find the new best mapping by adding one swap.""" candidates_evaluation = {} self._memory_map.append(deepcopy(self.circuit._circuit_logical)) - for candidate in self.swap_candidates(): - candidates_evaluation[candidate] = self.compute_cost(candidate) + for candidate in self._swap_candidates(): + candidates_evaluation[candidate] = self._compute_cost(candidate) best_cost = min(candidates_evaluation.values()) best_candidates = [ key for key, value in candidates_evaluation.items() if value == best_cost @@ -542,7 +540,7 @@ def find_new_mapping(self): self._delta_register[qubit] += self.delta self.circuit.update(best_candidate) - def compute_cost(self, candidate): + def _compute_cost(self, candidate): """Compute the cost associated to a possible SWAP candidate.""" temporary_circuit = deepcopy(self.circuit) temporary_circuit.update(candidate) @@ -551,7 +549,7 @@ def compute_cost(self, candidate): tot_distance = 0.0 weight = 1.0 for layer in range(self.lookahead + 1): - layer_gates = self.get_dag_layer(layer) + layer_gates = self._get_dag_layer(layer) avg_layer_distance = 0.0 for gate in layer_gates: qubits = temporary_circuit.get_physical_qubits(gate) @@ -564,7 +562,7 @@ def compute_cost(self, candidate): weight *= self.decay return tot_distance - def swap_candidates(self): + def _swap_candidates(self): """Return a list of possible candidate SWAPs (to be applied on logical qubits directly). The possible candidates are the ones sharing at least one qubit with a block in the front layer. """ @@ -585,7 +583,7 @@ def swap_candidates(self): candidates.append(candidate) return candidates - def check_execution(self): + def _check_execution(self): """Check if some gatesblocks in the front layer can be executed in the current configuration. Returns: @@ -603,7 +601,7 @@ def check_execution(self): return None return executable_blocks - def execute_blocks(self, blocklist: list): + def _execute_blocks(self, blocklist: list): """Execute a list of blocks: -Remove the correspondent nodes from the dag and circuit representation. -Add the executed blocks to the routed circuit. @@ -614,35 +612,15 @@ def execute_blocks(self, blocklist: list): block = self.circuit.circuit_blocks.search_by_index(block_id) self.circuit.execute_block(block) self._dag.remove_node(block_id) - self.update_dag_layers() - self.update_front_layer() + self._update_dag_layers() + self._update_front_layer() self._memory_map = [] self._delta_register = [1.0 for _ in self._delta_register] -def draw_dag(dag: nx.DiGraph, filename=None): # pragma: no cover - """Draw a direct acyclic graph in topological order. - - Args: - dag (nx.DiGraph): dag to be shown - filename (str): name of the saved image, if None the image will be showed. - """ - for layer, nodes in enumerate(nx.topological_generations(dag)): - for node in nodes: - dag.nodes[node]["layer"] = layer - pos = nx.multipartite_layout(dag, subset_key="layer") - fig, ax = plt.subplots() - nx.draw_networkx(dag, pos=pos, ax=ax) - ax.set_title("DAG layout in topological order") - fig.tight_layout() - if filename is None: - plt.show() - else: - plt.savefig(filename) - - -def create_dag(gates_qubits_pairs): - """Create direct acyclic graph (dag) of the circuit based on two qubit gates commutativity relations. +def _create_dag(gates_qubits_pairs): + """Helper method for :meth:`qibo.transpiler.router.Sabre`. + Create direct acyclic graph (dag) of the circuit based on two qubit gates commutativity relations. Args: gates_qubits_pairs (list): list of qubits tuples where gates/blocks acts. @@ -664,10 +642,10 @@ def create_dag(gates_qubits_pairs): if len(saturated_qubits) >= 2: break dag.add_edges_from(connectivity_list) - return remove_redundant_connections(dag) + return _remove_redundant_connections(dag) -def remove_redundant_connections(dag: nx.Graph): +def _remove_redundant_connections(dag: nx.Graph): """Remove redundant connection from a DAG unsing transitive reduction.""" new_dag = nx.DiGraph() new_dag.add_nodes_from(range(dag.number_of_nodes())) diff --git a/src/qibo/transpiler/star_connectivity.py b/src/qibo/transpiler/star_connectivity.py index 019631f0cb..ba4a18810c 100644 --- a/src/qibo/transpiler/star_connectivity.py +++ b/src/qibo/transpiler/star_connectivity.py @@ -4,25 +4,6 @@ from qibo.transpiler.router import ConnectivityError -def find_connected_qubit(qubits, queue, hardware_qubits): - """Helper method for :meth:`qibolab.transpilers.fix_connecivity`. - - Finds which qubit should be mapped to hardware middle qubit - by looking at the two-qubit gates that follow. - """ - possible_qubits = set(qubits) - for next_gate in queue: - if len(next_gate.qubits) == 2: - possible_qubits &= {hardware_qubits.index(q) for q in next_gate.qubits} - if not possible_qubits: - # freedom of choice - return qubits[0] - elif len(possible_qubits) == 1: - return possible_qubits.pop() - # freedom of choice - return qubits[0] - - class StarConnectivity(Router): """Transforms an arbitrary circuit to one that can be executed on hardware. This transpiler produces a circuit that respects the following connectivity: @@ -73,7 +54,7 @@ def __call__(self, circuit: Circuit, initial_layout=None): for i, gate in enumerate(circuit.queue): if len(gate.qubits) == 2: if middle_qubit not in gate.qubits: - new_middle = find_connected_qubit( + new_middle = _find_connected_qubit( gate.qubits, circuit.queue[i + 1 :], hardware_qubits ) hardware_qubits[middle_qubit], hardware_qubits[new_middle] = ( @@ -100,7 +81,7 @@ def __call__(self, circuit: Circuit, initial_layout=None): elif len(qubits) == 2 and middle_qubit not in qubits: # find which qubit should be moved to 0 - new_middle = find_connected_qubit( + new_middle = _find_connected_qubit( qubits, circuit.queue[i + 1 :], hardware_qubits ) # update hardware qubits according to the swap @@ -124,3 +105,22 @@ def __call__(self, circuit: Circuit, initial_layout=None): add_swap = True hardware_qubits_keys = ["q" + str(i) for i in range(5)] return new, dict(zip(hardware_qubits_keys, hardware_qubits)) + + +def _find_connected_qubit(qubits, queue, hardware_qubits): + """Helper method for :meth:`qibo.transpiler.StarConnectivity`. + + Finds which qubit should be mapped to hardware middle qubit + by looking at the two-qubit gates that follow. + """ + possible_qubits = set(qubits) + for next_gate in queue: + if len(next_gate.qubits) == 2: + possible_qubits &= {hardware_qubits.index(q) for q in next_gate.qubits} + if not possible_qubits: + # freedom of choice + return qubits[0] + elif len(possible_qubits) == 1: + return possible_qubits.pop() + # freedom of choice + return qubits[0] From 6517f5bd3ac1ba12f1385bcbf9e95b816f1a044f Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 7 Nov 2023 15:02:25 +0400 Subject: [PATCH 34/55] fix tests --- src/qibo/transpiler/placer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 822d24f2c4..12c2260a4c 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -221,7 +221,7 @@ def __call__(self, circuit): final_cost = cost return final_mapping - def _cost(graph, gates_qubits_pairs): + def _cost(self, graph, gates_qubits_pairs): """ Compute the cost associated to an initial layout as the lengh of the reduced circuit. From b93746a9113b33c0b3abb08103cb31b60fbef29b Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 7 Nov 2023 15:17:43 +0400 Subject: [PATCH 35/55] fix tests --- tests/test_transpiler_blocks.py | 59 ++++++++++------------ tests/test_transpiler_router.py | 4 +- tests/test_transpiler_star_connectivity.py | 6 +-- 3 files changed, 32 insertions(+), 37 deletions(-) diff --git a/tests/test_transpiler_blocks.py b/tests/test_transpiler_blocks.py index 51000fa885..2bb701992a 100644 --- a/tests/test_transpiler_blocks.py +++ b/tests/test_transpiler_blocks.py @@ -6,15 +6,15 @@ Block, BlockingError, CircuitBlocks, + _check_multi_qubit_measurements, + _count_multi_qubit_gates, _find_previous_gates, _find_successive_gates, + _gates_on_qubit, + _initial_block_decomposition, + _remove_gates, + _split_multi_qubit_measurements, block_decomposition, - check_multi_qubit_measurements, - count_multi_qubit_gates, - gates_on_qubit, - initial_block_decomposition, - remove_gates, - split_multi_qubit_measurements, ) @@ -27,7 +27,7 @@ def assert_gates_equality(gates_1: list, gates_2: list): def test_count_2q_gates(): block = Block(qubits=(0, 1), gates=[gates.CZ(0, 1), gates.CZ(0, 1), gates.H(0)]) - assert block.count_2q_gates() == 2 + assert block._count_2q_gates() == 2 def test_rename(): @@ -41,7 +41,7 @@ def test_add_gate_and_entanglement(): assert block.entangled == False block.add_gate(gates.CZ(0, 1)) assert block.entangled == True - assert block.count_2q_gates() == 1 + assert block._count_2q_gates() == 1 def test_add_gate_error(): @@ -79,21 +79,21 @@ def test_commute_true(): def test_count_multi_qubit_gates(): gatelist = [gates.CZ(0, 1), gates.H(0), gates.TOFFOLI(0, 1, 2)] - assert count_multi_qubit_gates(gatelist) == 2 + assert _count_multi_qubit_gates(gatelist) == 2 def test_gates_on_qubit(): gatelist = [gates.H(0), gates.H(1), gates.H(2), gates.H(0)] - assert_gates_equality(gates_on_qubit(gatelist, 0), [gatelist[0], gatelist[-1]]) - assert_gates_equality(gates_on_qubit(gatelist, 1), [gatelist[1]]) - assert_gates_equality(gates_on_qubit(gatelist, 2), [gatelist[2]]) + assert_gates_equality(_gates_on_qubit(gatelist, 0), [gatelist[0], gatelist[-1]]) + assert_gates_equality(_gates_on_qubit(gatelist, 1), [gatelist[1]]) + assert_gates_equality(_gates_on_qubit(gatelist, 2), [gatelist[2]]) def test_remove_gates(): gatelist = [gates.H(0), gates.CZ(0, 1), gates.H(2), gates.CZ(0, 2)] remaining = [gates.CZ(0, 1), gates.H(2)] delete_list = [gatelist[0], gatelist[3]] - remove_gates(gatelist, delete_list) + _remove_gates(gatelist, delete_list) assert_gates_equality(gatelist, remaining) @@ -118,7 +118,7 @@ def test_initial_block_decomposition(): circ.add(gates.CZ(1, 2)) circ.add(gates.H(3)) circ.add(gates.H(4)) - blocks = initial_block_decomposition(circ) + blocks = _initial_block_decomposition(circ) assert_gates_equality(blocks[0].gates, [gates.H(1), gates.H(0), gates.CZ(0, 1)]) assert len(blocks) == 4 assert len(blocks[0].gates) == 3 @@ -132,19 +132,19 @@ def test_check_measurements(): circ = Circuit(2) circ.add(gates.H(1)) circ.add(gates.M(0, 1)) - assert check_multi_qubit_measurements(circ) + assert _check_multi_qubit_measurements(circ) circ = Circuit(2) circ.add(gates.H(1)) circ.add(gates.M(0)) circ.add(gates.M(1)) - assert not check_multi_qubit_measurements(circ) + assert not _check_multi_qubit_measurements(circ) def test_split_measurements(): circ = Circuit(2) circ.add(gates.H(1)) circ.add(gates.M(0, 1)) - new_circ = split_multi_qubit_measurements(circ) + new_circ = _split_multi_qubit_measurements(circ) assert_gates_equality(new_circ.queue, [gates.H(1), gates.M(0), gates.M(1)]) @@ -160,7 +160,7 @@ def test_initial_block_decomposition_measurements(): circ.add(gates.M(3)) circ.add(gates.H(3)) circ.add(gates.H(4)) - blocks = initial_block_decomposition(circ) + blocks = _initial_block_decomposition(circ) print(blocks[0].gates) assert_gates_equality( blocks[0].gates, @@ -175,7 +175,7 @@ def test_initial_block_decomposition_error(): circ.add(gates.TOFFOLI(0, 1, 2)) print(len(circ.queue[0].qubits)) with pytest.raises(BlockingError): - blocks = initial_block_decomposition(circ) + blocks = _initial_block_decomposition(circ) def test_block_decomposition_error(): @@ -228,15 +228,15 @@ def test_block_decomposition(): [gates.H(1), gates.H(0), gates.CZ(0, 1), gates.H(0), gates.CZ(0, 1)], ) assert len(blocks) == 4 - assert blocks[0].count_2q_gates() == 2 + assert blocks[0]._count_2q_gates() == 2 assert len(blocks[0].gates) == 5 assert blocks[0].qubits == (0, 1) - assert blocks[1].count_2q_gates() == 2 + assert blocks[1]._count_2q_gates() == 2 assert len(blocks[1].gates) == 3 - assert blocks[3].count_2q_gates() == 1 + assert blocks[3]._count_2q_gates() == 1 assert len(blocks[3].gates) == 2 assert blocks[3].qubits == (2, 3) - assert blocks[2].count_2q_gates() == 3 + assert blocks[2]._count_2q_gates() == 3 assert len(blocks[2].gates) == 3 @@ -262,15 +262,15 @@ def test_block_decomposition_measurements(): [gates.H(1), gates.H(0), gates.CZ(0, 1), gates.H(0), gates.M(0), gates.M(1)], ) assert len(blocks) == 4 - assert blocks[0].count_2q_gates() == 1 + assert blocks[0]._count_2q_gates() == 1 assert len(blocks[0].gates) == 6 assert blocks[0].qubits == (0, 1) - assert blocks[1].count_2q_gates() == 2 + assert blocks[1]._count_2q_gates() == 2 assert len(blocks[1].gates) == 3 - assert blocks[3].count_2q_gates() == 1 + assert blocks[3]._count_2q_gates() == 1 assert len(blocks[3].gates) == 2 assert blocks[3].qubits == (2, 3) - assert blocks[2].count_2q_gates() == 2 + assert blocks[2]._count_2q_gates() == 2 assert len(blocks[2].gates) == 4 @@ -293,11 +293,6 @@ def test_circuit_blocks(backend): for index, block in enumerate(circuit_blocks()): assert block.name == index reconstructed_circ = circuit_blocks.circuit() - # Here we can't use assert gates_equality because the order of the gates is changed - # np.testing.assert_allclose( - # np.array(circ().state(), dtype=float), - # np.array(reconstructed_circ().state(), dtype=float), - # ) backend.assert_allclose( backend.execute_circuit(circ).state(), backend.execute_circuit(reconstructed_circ).state(), diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 2776e37651..3a5b8f50c4 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -326,7 +326,7 @@ def test_sabre_memory_map(): layout_circ = Circuit(5) initial_layout = placer(layout_circ) router = Sabre(connectivity=star_connectivity()) - router.preprocessing(circuit=star_circuit(), initial_layout=initial_layout) + router._preprocessing(circuit=star_circuit(), initial_layout=initial_layout) router._memory_map = [[1, 0, 2, 3, 4]] - value = router.compute_cost((0, 1)) + value = router._compute_cost((0, 1)) assert value == float("inf") diff --git a/tests/test_transpiler_star_connectivity.py b/tests/test_transpiler_star_connectivity.py index 9420b35b56..5a8e9a4b57 100644 --- a/tests/test_transpiler_star_connectivity.py +++ b/tests/test_transpiler_star_connectivity.py @@ -7,7 +7,7 @@ from qibo.backends import NumpyBackend from qibo.models import Circuit from qibo.quantum_info.random_ensembles import random_unitary -from qibo.transpiler.pipeline import transpose_qubits +from qibo.transpiler.pipeline import _transpose_qubits from qibo.transpiler.router import ConnectivityError from qibo.transpiler.star_connectivity import StarConnectivity @@ -63,7 +63,7 @@ def test_fix_connectivity(nqubits, depth, middle_qubit, measurements): final_state = backend.execute_circuit(transpiled).state() target_state = backend.execute_circuit(original).state() hardware_qubits = list(hardware_qubits.values()) - target_state = transpose_qubits(target_state, hardware_qubits) + target_state = _transpose_qubits(target_state, hardware_qubits) np.testing.assert_allclose(final_state, target_state) @@ -94,5 +94,5 @@ def test_fix_connectivity_unitaries(nqubits, unitary_dim, depth, middle_qubit): final_state = backend.execute_circuit(transpiled).state() target_state = backend.execute_circuit(original).state() hardware_qubits = list(hardware_qubits.values()) - target_state = transpose_qubits(target_state, hardware_qubits) + target_state = _transpose_qubits(target_state, hardware_qubits) np.testing.assert_allclose(final_state, target_state) From 0da552ef9bb568f318090fa5b349d1d6e83bdd79 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 9 Nov 2023 14:17:43 +0400 Subject: [PATCH 36/55] moving exceptions to single file --- src/qibo/transpiler/abstract.py | 20 +++++++------- src/qibo/transpiler/blocks.py | 5 +--- src/qibo/transpiler/exceptions.py | 21 +++++++++++++++ src/qibo/transpiler/optimizer.py | 2 +- src/qibo/transpiler/pipeline.py | 26 ++++++++++--------- src/qibo/transpiler/placer.py | 5 +--- src/qibo/transpiler/router.py | 5 +--- src/qibo/transpiler/unitary_decompositions.py | 1 - src/qibo/transpiler/unroller.py | 5 +--- 9 files changed, 50 insertions(+), 40 deletions(-) create mode 100644 src/qibo/transpiler/exceptions.py diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py index 6bef0e78a3..83e25d454d 100644 --- a/src/qibo/transpiler/abstract.py +++ b/src/qibo/transpiler/abstract.py @@ -34,10 +34,10 @@ def find_gates_qubits_pairs(circuit: Circuit): """Translate qibo circuit into a list of pairs of qubits to be used by the router and placer. Args: - circuit (qibo.models.Circuit): circuit to be transpiled. + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. Returns: - translated_circuit (list): list containing qubits targeted by two qubit gates + (list): list containing qubits targeted by two qubit gates. """ translated_circuit = [] for gate in circuit.queue: @@ -49,6 +49,7 @@ def find_gates_qubits_pairs(circuit: Circuit): raise_error( ValueError, "Gates targeting more than 2 qubits are not supported" ) + return translated_circuit @@ -62,10 +63,10 @@ def __call__(self, circuit: Circuit, *args) -> dict: """Find initial qubit mapping Args: - circuit (qibo.models.Circuit): circuit to be mapped. + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be mapped. Returns: - initial_layout (dict): dictionary containing the initial logical to physical qubit mapping. + (dict): dictionary containing the initial logical to physical qubit mapping. """ @@ -85,8 +86,7 @@ def __call__( initial_layout (dict): dictionary containing the initial logical to physical qubit mapping. Returns: - matched_circuit (qibo.models.Circuit): routed circuit - final_layout (dict): dictionary containing the final logical to physical qubit mapping. + (:class:`qibo.models.circuit.Circuit`, dict): routed circuit and dictionary containing the final logical to physical qubit mapping. """ @@ -98,10 +98,10 @@ def __call__(self, circuit: Circuit, *args) -> Circuit: """Find initial qubit mapping Args: - circuit (qibo.models.Circuit): circuit to be optimized + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be optimized Returns: - optimized_circuit (qibo.models.Circuit): circuit with optimized number of gates. + (:class:`qibo.models.circuit.Circuit`): circuit with optimized number of gates. """ @@ -115,8 +115,8 @@ def __call__(self, circuit: Circuit, *args) -> Circuit: """Find initial qubit mapping Args: - circuit (qibo.models.Circuit): circuit to be optimized + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be optimized Returns: - translated_circuit (qibo.models.Circuit): circuit with native gates. + (:class:`qibo.models.circuit.Circuit`): circuit with native gates. """ diff --git a/src/qibo/transpiler/blocks.py b/src/qibo/transpiler/blocks.py index 39b6101146..831560d9c8 100644 --- a/src/qibo/transpiler/blocks.py +++ b/src/qibo/transpiler/blocks.py @@ -2,10 +2,7 @@ from qibo import Circuit, gates from qibo.gates import Gate - - -class BlockingError(Exception): - """Raise when an error occurs in the blocking procedure""" +from qibo.transpiler.exceptions import BlockingError class Block: diff --git a/src/qibo/transpiler/exceptions.py b/src/qibo/transpiler/exceptions.py new file mode 100644 index 0000000000..f99f67605f --- /dev/null +++ b/src/qibo/transpiler/exceptions.py @@ -0,0 +1,21 @@ +"""Custom exceptions raised in transpiler routines.""" + + +class BlockingError(Exception): + """Raise when an error occurs in the blocking procedure""" + + +class ConnectivityError(Exception): + """Raise for an error in the connectivity""" + + +class DecompositionError(Exception): + """A decomposition error is raised when, during transpiling, gates are not correctly decomposed in native gates""" + + +class PlacementError(Exception): + """Raise for an error in the initial qubit placement""" + + +class TranspilerPipelineError(Exception): + """Raise when an error occurs in the transpiler pipeline""" diff --git a/src/qibo/transpiler/optimizer.py b/src/qibo/transpiler/optimizer.py index d2525ff289..16cd1ff7bd 100644 --- a/src/qibo/transpiler/optimizer.py +++ b/src/qibo/transpiler/optimizer.py @@ -9,7 +9,7 @@ class Preprocessing(Optimizer): """Match the number of qubits of the circuit with the number of qubits of the chip if possible. Args: - connectivity (nx.Graph): hardware chip connectivity. + connectivity (:class:`networkx.Graph`): hardware chip connectivity. """ def __init__(self, connectivity: nx.Graph): diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index 96a7459a27..6e79c7ae0b 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -1,10 +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.abstract import NativeType, Optimizer, Placer, Router, Unroller +from qibo.transpiler.exceptions import TranspilerPipelineError from qibo.transpiler.optimizer import Preprocessing from qibo.transpiler.placer import Trivial, assert_placement from qibo.transpiler.router import ConnectivityError, assert_connectivity @@ -16,26 +20,24 @@ ) -class TranspilerPipelineError(Exception): - """Raise when an error occurs in the transpiler pipeline""" - - def assert_circuit_equivalence( original_circuit: Circuit, transpiled_circuit: Circuit, final_map: dict, - initial_map: dict = None, - test_states: list = None, + initial_map: Optional[dict] = None, + test_states: Optional[list] = None, ntests: int = 3, ): """Checks that the transpiled circuit agrees with the original using simulation. Args: - original_circuit (qibo.models.Circuit): Original circuit. - transpiled_circuit (qibo.models.Circuit): Transpiled circuit. + 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): logical_physical qubit mapping before routing, if None trivial initial map is used. - test_states (list): states on which the test is performed, if None 'ntests' random states will be tested. + 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): number of random states tested. """ backend = NumpyBackend() @@ -72,7 +74,7 @@ def assert_circuit_equivalence( try: np.testing.assert_allclose(fidelity, 1.0) except AssertionError: - raise TranspilerPipelineError("Circuit equivalence not satisfied.") + raise_error(TranspilerPipelineError, "Circuit equivalence not satisfied.") def _transpose_qubits(state: np.ndarray, qubits_ordering: np.ndarray): @@ -102,7 +104,7 @@ def assert_transpiling( Args: original_circuit (qibo.models.Circuit): circuit before transpiling. transpiled_circuit (qibo.models.Circuit): circuit after transpiling. - connectivity (nx.Graph): chip qubits connectivity. + connectivity (networkx.Graph): chip qubits connectivity. initial_layout (dict): initial physical-logical qubit mapping. final_layout (dict): final physical-logical qubit mapping. native_gates: (NativeType): native gates supported by the hardware. diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 12c2260a4c..75517a5433 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -6,10 +6,7 @@ from qibo.config import raise_error from qibo.models import Circuit from qibo.transpiler.abstract import Placer, Router, find_gates_qubits_pairs - - -class PlacementError(Exception): - """Raise for an error in the initial qubit placement""" +from qibo.transpiler.exceptions import PlacementError def assert_placement(circuit: Circuit, layout: dict) -> bool: diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 284750a9ea..78890746d3 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -11,13 +11,10 @@ from qibo.models import Circuit from qibo.transpiler.abstract import Router, find_gates_qubits_pairs from qibo.transpiler.blocks import Block, CircuitBlocks +from qibo.transpiler.exceptions import ConnectivityError from qibo.transpiler.placer import assert_placement -class ConnectivityError(Exception): - """Raise for an error in the connectivity""" - - 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. diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py index 0ff1758ff1..0dfc0b29f2 100644 --- a/src/qibo/transpiler/unitary_decompositions.py +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -1,5 +1,4 @@ import numpy as np -from scipy.linalg import expm from qibo import gates, matrices from qibo.backends import GlobalBackend, NumpyBackend diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 7e7d93e1ac..e7ba8ce928 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -5,6 +5,7 @@ from qibo.config import raise_error from qibo.models import Circuit from qibo.transpiler.abstract import NativeType, Unroller +from qibo.transpiler.exceptions import DecompositionError from qibo.transpiler.unitary_decompositions import ( two_qubit_decomposition, u3_decomposition, @@ -57,10 +58,6 @@ def __call__(self, circuit: Circuit): return translated_circuit -class DecompositionError(Exception): - """A decomposition error is raised when, during transpiling, gates are not correctly decomposed in native gates""" - - def assert_decomposition( circuit: Circuit, two_qubit_natives: NativeType, From 6582debb192ed87e8ff0b66714d49d50504c65ab Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 9 Nov 2023 14:24:48 +0400 Subject: [PATCH 37/55] rename function and clean imports --- src/qibo/transpiler/abstract.py | 46 ++++++++++++------------ src/qibo/transpiler/placer.py | 8 ++--- src/qibo/transpiler/router.py | 4 +-- src/qibo/transpiler/star_connectivity.py | 1 - tests/test_transpiler_abstract.py | 6 ++-- 5 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py index 83e25d454d..d9daae15b0 100644 --- a/src/qibo/transpiler/abstract.py +++ b/src/qibo/transpiler/abstract.py @@ -30,29 +30,6 @@ def from_gate(cls, gate: gates.Gate): raise ValueError(f"Gate {gate} cannot be used as native.") -def find_gates_qubits_pairs(circuit: Circuit): - """Translate qibo circuit into a list of pairs of qubits to be used by the router and placer. - - Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. - - Returns: - (list): list containing qubits targeted by two qubit gates. - """ - translated_circuit = [] - for gate in circuit.queue: - if isinstance(gate, gates.M): - pass - elif len(gate.qubits) == 2: - translated_circuit.append(sorted(gate.qubits)) - elif len(gate.qubits) >= 3: - raise_error( - ValueError, "Gates targeting more than 2 qubits are not supported" - ) - - return translated_circuit - - class Placer(ABC): @abstractmethod def __init__(self, connectivity: nx.Graph, *args): @@ -120,3 +97,26 @@ def __call__(self, circuit: Circuit, *args) -> Circuit: Returns: (:class:`qibo.models.circuit.Circuit`): circuit with native gates. """ + + +def _find_gates_qubits_pairs(circuit: Circuit): + """Translate qibo circuit into a list of pairs of qubits to be used by the router and placer. + + Args: + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. + + Returns: + (list): list containing qubits targeted by two qubit gates. + """ + translated_circuit = [] + for gate in circuit.queue: + if isinstance(gate, gates.M): + pass + elif len(gate.qubits) == 2: + translated_circuit.append(sorted(gate.qubits)) + elif len(gate.qubits) >= 3: + raise_error( + ValueError, "Gates targeting more than 2 qubits are not supported" + ) + + return translated_circuit diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 75517a5433..07a91cd02a 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -5,7 +5,7 @@ from qibo import gates from qibo.config import raise_error from qibo.models import Circuit -from qibo.transpiler.abstract import Placer, Router, find_gates_qubits_pairs +from qibo.transpiler.abstract import Placer, Router, _find_gates_qubits_pairs from qibo.transpiler.exceptions import PlacementError @@ -143,7 +143,7 @@ def __call__(self, circuit: Circuit): Returns: (dict): physical to logical qubit mapping. """ - gates_qubits_pairs = find_gates_qubits_pairs(circuit) + gates_qubits_pairs = _find_gates_qubits_pairs(circuit) if len(gates_qubits_pairs) < 3: raise_error( ValueError, @@ -197,7 +197,7 @@ def __call__(self, circuit): Returns: (dict): physical to logical qubit mapping. """ - gates_qubits_pairs = find_gates_qubits_pairs(circuit) + gates_qubits_pairs = _find_gates_qubits_pairs(circuit) nodes = self.connectivity.number_of_nodes() keys = list(self.connectivity.nodes()) final_mapping = dict(zip(keys, range(nodes))) @@ -285,7 +285,7 @@ def _assemble_circuit(self, circuit: Circuit): if self.depth is None: return circuit.invert() - gates_qubits_pairs = find_gates_qubits_pairs(circuit) + gates_qubits_pairs = _find_gates_qubits_pairs(circuit) circuit_gates = len(gates_qubits_pairs) if circuit_gates == 0: raise ValueError("The circuit must contain at least a two qubit gate.") diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 78890746d3..46783f74b2 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -9,7 +9,7 @@ from qibo import gates from qibo.config import log, raise_error from qibo.models import Circuit -from qibo.transpiler.abstract import Router, find_gates_qubits_pairs +from qibo.transpiler.abstract import Router, _find_gates_qubits_pairs from qibo.transpiler.blocks import Block, CircuitBlocks from qibo.transpiler.exceptions import ConnectivityError from qibo.transpiler.placer import assert_placement @@ -86,7 +86,7 @@ def __call__(self, circuit: Circuit, initial_layout): self._mapping = initial_layout init_qubit_map = np.asarray(list(initial_layout.values())) self._initial_checks(circuit.nqubits) - self._gates_qubits_pairs = find_gates_qubits_pairs(circuit) + self._gates_qubits_pairs = _find_gates_qubits_pairs(circuit) self._mapping = dict(zip(range(len(initial_layout)), initial_layout.values())) self._graph = nx.relabel_nodes(self.connectivity, self._mapping) self._qubit_map = np.sort(init_qubit_map) diff --git a/src/qibo/transpiler/star_connectivity.py b/src/qibo/transpiler/star_connectivity.py index ba4a18810c..7c03aa1d42 100644 --- a/src/qibo/transpiler/star_connectivity.py +++ b/src/qibo/transpiler/star_connectivity.py @@ -1,5 +1,4 @@ from qibo import Circuit, gates -from qibo.backends import NumpyBackend from qibo.transpiler.abstract import Router from qibo.transpiler.router import ConnectivityError diff --git a/tests/test_transpiler_abstract.py b/tests/test_transpiler_abstract.py index 2228fa4f98..9967e5295a 100644 --- a/tests/test_transpiler_abstract.py +++ b/tests/test_transpiler_abstract.py @@ -2,7 +2,7 @@ from qibo import gates from qibo.models import Circuit -from qibo.transpiler.abstract import find_gates_qubits_pairs +from qibo.transpiler.abstract import _find_gates_qubits_pairs def test_circuit_representation(): @@ -12,7 +12,7 @@ def test_circuit_representation(): circuit.add(gates.X(1)) circuit.add(gates.CZ(3, 0)) circuit.add(gates.CNOT(4, 0)) - repr = find_gates_qubits_pairs(circuit) + repr = _find_gates_qubits_pairs(circuit) assert repr == [[0, i + 1] for i in range(4)] @@ -20,4 +20,4 @@ def test_circuit_representation_fail(): circuit = Circuit(5) circuit.add(gates.TOFFOLI(0, 1, 2)) with pytest.raises(ValueError): - repr = find_gates_qubits_pairs(circuit) + repr = _find_gates_qubits_pairs(circuit) From 4e8af6b686020d266ad7215678024ab1f4d72a50 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 9 Nov 2023 14:32:24 +0400 Subject: [PATCH 38/55] remove `rename` method --- src/qibo/transpiler/blocks.py | 27 +++++++++++++-------------- tests/test_transpiler_blocks.py | 6 ------ 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/qibo/transpiler/blocks.py b/src/qibo/transpiler/blocks.py index 831560d9c8..5043e5abdd 100644 --- a/src/qibo/transpiler/blocks.py +++ b/src/qibo/transpiler/blocks.py @@ -1,6 +1,7 @@ from typing import Optional, Union from qibo import Circuit, gates +from qibo.config import raise_error from qibo.gates import Gate from qibo.transpiler.exceptions import BlockingError @@ -11,10 +12,7 @@ class Block: Args: qubits (tuple): qubits where the block is acting. gates (list): list of gates that compose the block. - name (str or int): name of the block. - - Properties: - entangled (bool): True if the block entangles the qubits (there is at least one two qubit gate). + name (str or int, optional): name of the block. """ def __init__( @@ -26,20 +24,21 @@ def __init__( @property def entangled(self): - """True if the block contains two qubit gates.""" + """Returns ``True`` if the block contains two-qubit gates.""" return self._count_2q_gates() > 0 - def rename(self, name): - """Rename block""" - self.name = name def add_gate(self, gate: Gate): - """Add a new gate to the block.""" + """Add a new gate to the block. + + Args: + gate (:class:`qibo.gates.abstract.Gate`): gate to be added. + """ if not set(gate.qubits).issubset(self.qubits): - raise BlockingError( - "Gate acting on qubits {} can't be added to block acting on qubits {}.".format( - gate.qubits, self._qubits - ) + raise_error( + BlockingError, + f"Gate acting on qubits {gate.qubis} can't be added " + + f"to block acting on qubits {self._qubits}.", ) self.gates.append(gate) @@ -119,7 +118,7 @@ def __init__(self, circuit: Circuit, index_names: bool = False): self._index_names = index_names if index_names: for index, block in enumerate(self.block_list): - block.rename(index) + block.name = index self.qubits = circuit.nqubits def __call__(self): diff --git a/tests/test_transpiler_blocks.py b/tests/test_transpiler_blocks.py index 2bb701992a..4d2446c806 100644 --- a/tests/test_transpiler_blocks.py +++ b/tests/test_transpiler_blocks.py @@ -30,12 +30,6 @@ def test_count_2q_gates(): assert block._count_2q_gates() == 2 -def test_rename(): - block = Block(qubits=(0, 1), gates=[gates.CZ(0, 1)]) - block.rename("renamed_block") - assert block.name == "renamed_block" - - def test_add_gate_and_entanglement(): block = Block(qubits=(0, 1), gates=[gates.H(0)]) assert block.entangled == False From 765a09fbfcc5acdb68316cada9706517984bc588 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 9 Nov 2023 10:32:46 +0000 Subject: [PATCH 39/55] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/transpiler/blocks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qibo/transpiler/blocks.py b/src/qibo/transpiler/blocks.py index 5043e5abdd..810ee0be45 100644 --- a/src/qibo/transpiler/blocks.py +++ b/src/qibo/transpiler/blocks.py @@ -27,7 +27,6 @@ def entangled(self): """Returns ``True`` if the block contains two-qubit gates.""" return self._count_2q_gates() > 0 - def add_gate(self, gate: Gate): """Add a new gate to the block. From 9f47f3991f7fd8640ed293e56df584a6350e4ff1 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 9 Nov 2023 14:43:47 +0400 Subject: [PATCH 40/55] docstring `blocks` --- src/qibo/transpiler/blocks.py | 50 +++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/src/qibo/transpiler/blocks.py b/src/qibo/transpiler/blocks.py index 810ee0be45..6c74a06621 100644 --- a/src/qibo/transpiler/blocks.py +++ b/src/qibo/transpiler/blocks.py @@ -47,30 +47,30 @@ def _count_2q_gates(self): @property def qubits(self): - """Return a sorted tuple with qubits of the block.""" + """Returns a sorted tuple with qubits of the block.""" return tuple(sorted(self._qubits)) @qubits.setter def qubits(self, qubits): self._qubits = qubits - def fuse(self, block: "Block", name: str = None): - """Fuse the current block with a new one, the qubits they are acting on must coincide. + def fuse(self, block, name: Optional[str] = None): + """Fuses the current block with a new one, the qubits they are acting on must coincide. Args: block (:class:`qibo.transpiler.blocks.Block`): block to fuse. - name (str): name of the fused block. + name (str, optional): name of the fused block. Return: - fused_block (:class:`qibo.transpiler.blocks.Block`): fusion of the two input blocks. + (:class:`qibo.transpiler.blocks.Block`): fusion of the two input blocks. """ if not self.qubits == block.qubits: - raise BlockingError( - "In order to fuse two blocks their qubits must coincide." + raise_error( + BlockingError, "In order to fuse two blocks their qubits must coincide." ) return Block(qubits=self.qubits, gates=self.gates + block.gates, name=name) - def on_qubits(self, new_qubits): + def on_qubits(self, new_qubits: tuple): """Return a new block acting on the new qubits. Args: @@ -78,10 +78,11 @@ def on_qubits(self, new_qubits): """ qubits_dict = dict(zip(self.qubits, new_qubits)) new_gates = [gate.on_qubits(qubits_dict) for gate in self.gates] + return Block(qubits=new_qubits, gates=new_gates, name=self.name) # TODO: use real QM properties to check commutation - def commute(self, block: "Block"): + def commute(self, block): """Check if a block commutes with the current one. Args: @@ -108,8 +109,8 @@ class CircuitBlocks: """A CircuitBlocks contains a quantum circuit decomposed in two qubits blocks. Args: - circuit (qibo.models.Circuit): circuit to be decomposed. - index_names (bool): assign names to the blocks + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be decomposed. + index_names (bool, optional): assign names to the blocks """ def __init__(self, circuit: Circuit, index_names: bool = False): @@ -155,8 +156,9 @@ def remove_block(self, block: "Block"): try: self.block_list.remove(block) except ValueError: - raise BlockingError( - "The block you are trying to remove is not present in the circuit blocks." + raise_error( + BlockingError, + "The block you are trying to remove is not present in the circuit blocks.", ) @@ -165,21 +167,25 @@ def block_decomposition(circuit: Circuit, fuse: bool = True): Break measurements on multiple qubits into measurements of single qubit. Args: - circuit (qibo.models.Circuit): circuit to be decomposed. - fuse (bool): fuse adjacent blocks acting on the same qubits. + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be decomposed. + fuse (bool, optional): fuse adjacent blocks acting on the same qubits. Return: - blocks (list): list of blocks that act on two qubits. + (list): list of blocks that act on two qubits. """ if circuit.nqubits < 2: - raise BlockingError( - "Only circuits with at least two qubits can be decomposed with block_decomposition." + raise_error( + BlockingError, + "Only circuits with at least two qubits can be decomposed with block_decomposition.", ) + if _check_multi_qubit_measurements(circuit): circuit = _split_multi_qubit_measurements(circuit) initial_blocks = _initial_block_decomposition(circuit) + if not fuse: return initial_blocks + blocks = [] while len(initial_blocks) > 0: first_block = initial_blocks[0] @@ -194,6 +200,7 @@ def block_decomposition(circuit: Circuit, fuse: bool = True): break blocks.append(first_block) _remove_gates(initial_blocks, remove_list) + return blocks @@ -223,9 +230,11 @@ def _initial_block_decomposition(circuit: Circuit): blocks.append(block) break elif len(gate.qubits) > 2: - raise BlockingError( - "Gates targeting more than 2 qubits are not supported." + raise_error( + BlockingError, + "Gates targeting more than 2 qubits are not supported.", ) + # Now we need to deal with the remaining spare single qubit gates while len(all_gates) > 0: first_qubit = all_gates[0].qubits[0] @@ -247,6 +256,7 @@ def _initial_block_decomposition(circuit: Circuit): gates=block_gates, ) blocks.append(block) + return blocks From 64cca8ef373548fd052e296851105ff6c88e1ff6 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 9 Nov 2023 14:45:21 +0400 Subject: [PATCH 41/55] fix typo --- src/qibo/transpiler/blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/transpiler/blocks.py b/src/qibo/transpiler/blocks.py index 6c74a06621..26a01b3780 100644 --- a/src/qibo/transpiler/blocks.py +++ b/src/qibo/transpiler/blocks.py @@ -36,7 +36,7 @@ def add_gate(self, gate: Gate): if not set(gate.qubits).issubset(self.qubits): raise_error( BlockingError, - f"Gate acting on qubits {gate.qubis} can't be added " + f"Gate acting on qubits {gate.qubits} can't be added " + f"to block acting on qubits {self._qubits}.", ) self.gates.append(gate) From d8c5de9673a2e5cf690914db281c9ea8ba7b3d82 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 9 Nov 2023 15:25:07 +0400 Subject: [PATCH 42/55] more docstring upgrades --- src/qibo/transpiler/blocks.py | 20 +-- src/qibo/transpiler/exceptions.py | 3 +- src/qibo/transpiler/optimizer.py | 15 +- src/qibo/transpiler/pipeline.py | 26 +-- src/qibo/transpiler/placer.py | 148 +++++++++++------- src/qibo/transpiler/router.py | 1 - src/qibo/transpiler/star_connectivity.py | 17 +- src/qibo/transpiler/unitary_decompositions.py | 4 +- 8 files changed, 137 insertions(+), 97 deletions(-) diff --git a/src/qibo/transpiler/blocks.py b/src/qibo/transpiler/blocks.py index 26a01b3780..7c0ec3d902 100644 --- a/src/qibo/transpiler/blocks.py +++ b/src/qibo/transpiler/blocks.py @@ -12,7 +12,7 @@ class Block: Args: qubits (tuple): qubits where the block is acting. gates (list): list of gates that compose the block. - name (str or int, optional): name of the block. + name (str or int, optional): name of the block. Defaults to ``None``. """ def __init__( @@ -59,7 +59,7 @@ def fuse(self, block, name: Optional[str] = None): Args: block (:class:`qibo.transpiler.blocks.Block`): block to fuse. - name (str, optional): name of the fused block. + name (str, optional): name of the fused block. Defaults to ``None``. Return: (:class:`qibo.transpiler.blocks.Block`): fusion of the two input blocks. @@ -102,7 +102,7 @@ def kak_decompose(self): # pragma: no cover This should be done only if the block is entangled and the number of two qubit gates is higher than the number after the decomposition. """ - raise NotImplementedError + raise_error(NotImplementedError, "") class CircuitBlocks: @@ -110,7 +110,7 @@ class CircuitBlocks: Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be decomposed. - index_names (bool, optional): assign names to the blocks + index_names (bool, optional): assign names to the blocks. Defaults to ``False``. """ def __init__(self, circuit: Circuit, index_names: bool = False): @@ -127,19 +127,21 @@ def __call__(self): def search_by_index(self, index: int): """Find a block from its index, requires index_names == True""" if not self._index_names: - raise BlockingError( - "You need to assign index names in order to use search_by_index." + raise_error( + BlockingError, + "You need to assign index names in order to use search_by_index.", ) for block in self.block_list: if block.name == index: return block - raise BlockingError("No block found with index {}.".format(index)) + raise_error(BlockingError, f"No block found with index {index}.") def add_block(self, block: "Block"): """Add a two qubits block.""" if not set(block.qubits).issubset(range(self.qubits)): - raise BlockingError( - "The block can't be added to the circuit because it acts on different qubits" + raise_error( + BlockingError, + "The block can't be added to the circuit because it acts on different qubits", ) self.block_list.append(block) diff --git a/src/qibo/transpiler/exceptions.py b/src/qibo/transpiler/exceptions.py index f99f67605f..2d81c81dc7 100644 --- a/src/qibo/transpiler/exceptions.py +++ b/src/qibo/transpiler/exceptions.py @@ -10,7 +10,8 @@ class ConnectivityError(Exception): class DecompositionError(Exception): - """A decomposition error is raised when, during transpiling, gates are not correctly decomposed in native gates""" + """A decomposition error is raised when, during transpiling, + gates are not correctly decomposed in native gates""" class PlacementError(Exception): diff --git a/src/qibo/transpiler/optimizer.py b/src/qibo/transpiler/optimizer.py index 16cd1ff7bd..7008957627 100644 --- a/src/qibo/transpiler/optimizer.py +++ b/src/qibo/transpiler/optimizer.py @@ -1,6 +1,7 @@ import networkx as nx from qibo import gates +from qibo.config import raise_error from qibo.models import Circuit from qibo.transpiler.abstract import Optimizer @@ -19,8 +20,10 @@ def __call__(self, circuit: Circuit) -> Circuit: physical_qubits = self.connectivity.number_of_nodes() logical_qubits = circuit.nqubits if logical_qubits > physical_qubits: - raise ValueError( - "The number of qubits in the circuit can't be greater than the number of physical qubits." + raise_error( + ValueError, + "The number of qubits in the circuit can't be greater " + + "than the number of physical qubits.", ) if logical_qubits == physical_qubits: return circuit @@ -34,15 +37,19 @@ class Rearrange(Optimizer): """Rearranges gates using qibo's fusion algorithm. May reduce number of SWAPs when fixing for connectivity but this has not been tested. + + Args: + max_qubits (int, optional): maximum number of qubits to fuse gates. + Defaults to :math:`1`. """ def __init__(self, max_qubits: int = 1): self.max_qubits = max_qubits def __call__(self, circuit: Circuit): - fcircuit = circuit.fuse(max_qubits=self.max_qubits) + fused_circuit = circuit.fuse(max_qubits=self.max_qubits) new = circuit.__class__(circuit.nqubits) - for fgate in fcircuit.queue: + for fgate in fused_circuit.queue: if isinstance(fgate, gates.FusedGate): new.add(gates.Unitary(fgate.matrix(), *fgate.qubits)) else: diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index 6e79c7ae0b..3813388559 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -38,13 +38,14 @@ def assert_circuit_equivalence( 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): number of random states tested. + 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 ValueError( - "Transpiled and original circuit do not have the same number of qubits." + raise_error( + ValueError, + "Transpiled and original circuit do not have the same number of qubits.", ) if test_states is None: @@ -150,8 +151,9 @@ def __init__( def default(self, connectivity: nx.Graph): """Return the default transpiler pipeline for the required hardware connectivity.""" if not isinstance(connectivity, nx.Graph): - raise TranspilerPipelineError( - "Define the hardware chip connectivity to use default transpiler" + raise_error( + TranspilerPipelineError, + "Define the hardware chip connectivity to use default transpiler", ) default_passes = [] # preprocessing @@ -176,8 +178,9 @@ def __call__(self, circuit): if self.initial_layout == None: self.initial_layout = transpiler_pass(circuit) else: - raise TranspilerPipelineError( - "You are defining more than one placer pass." + raise_error( + TranspilerPipelineError, + "You are defining more than one placer pass.", ) elif isinstance(transpiler_pass, Router): if self.initial_layout is not None: @@ -185,14 +188,15 @@ def __call__(self, circuit): circuit, self.initial_layout ) else: - raise TranspilerPipelineError( - "Use a placement pass before routing." + raise_error( + TranspilerPipelineError, "Use a placement pass before routing." ) elif isinstance(transpiler_pass, Unroller): circuit = transpiler_pass(circuit) else: - raise TranspilerPipelineError( - "Unrecognised transpiler pass: ", transpiler_pass + raise_error( + TranspilerPipelineError, + f"Unrecognised transpiler pass: {transpiler_pass}", ) return circuit, final_layout diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 07a91cd02a..1aa9c671e9 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -1,4 +1,5 @@ import random +from typing import Optional, Union import networkx as nx @@ -13,50 +14,51 @@ def assert_placement(circuit: Circuit, layout: dict) -> bool: """Check if layout is in the correct form and matches the number of qubits of the circuit. Args: - circuit (qibo.models.Circuit): Circuit model to check. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit model to check. layout (dict): physical to logical qubit mapping. - - Raise PlacementError if the following conditions are not satisfied: - - layout is written in the correct form. - - layout matches the number of qubits in the circuit. """ assert_mapping_consistency(layout) if circuit.nqubits > len(layout): - raise PlacementError( - "Layout can't be used on circuit. The circuit requires more qubits." + raise_error( + PlacementError, + "Layout can't be used on circuit. The circuit requires more qubits.", ) if circuit.nqubits < len(layout): - raise PlacementError( - "Layout can't be used on circuit. Ancillary extra qubits need to be added to the circuit." + 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): +def assert_mapping_consistency(layout: dict): """Check if layout is in the correct form. Args: layout (dict): physical to logical qubit mapping. - - Raise PlacementError if layout is not written in the correct form. """ values = sorted(layout.values()) keys = list(layout) ref_keys = ["q" + str(i) for i in range(len(keys))] if keys != ref_keys: - raise PlacementError( - "Some physical qubits in the layout may be missing or duplicated." + raise_error( + PlacementError, + "Some physical qubits in the layout may be missing or duplicated.", ) if values != list(range(len(values))): - raise PlacementError( - "Some logical qubits in the layout may be missing or duplicated." + raise_error( + PlacementError, + "Some logical qubits in the layout may be missing or duplicated.", ) class Trivial(Placer): - """Place qubits according to the following simple notation: {'q0' : 0, 'q1' : 1, ..., 'qn' : n}. + """Place qubits according to the following notation: - Attributes: - connectivity (networkx.Graph): chip connectivity. + .. math:: + \\{\\textup{"q0"} : 0, \\textup{"q1"} : 1, ..., \\textup{"qn"} : n}. + + Args: + connectivity (networkx.Graph, optional): chip connectivity. """ def __init__(self, connectivity: nx.Graph = None): @@ -66,15 +68,17 @@ def __call__(self, circuit: Circuit): """Find the trivial placement for the circuit. Args: - circuit (qibo.models.Circuit): circuit to be transpiled. + 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 PlacementError( - "The number of nodes of the connectivity graph must match the number of qubits in the circuit" + raise_error( + PlacementError, + "The number of nodes of the connectivity graph must match " + + "the number of qubits in the circuit", ) return dict( zip( @@ -87,14 +91,16 @@ def __call__(self, circuit: Circuit): class Custom(Placer): """Define a custom initial qubit mapping. - Attributes: - map (list or dict): physical to logical qubit mapping, - example [1,2,0] or {"q0":1, "q1":2, "q2":0} to assign the - physical qubits 0;1;2 to the logical qubits 1;2;0 respectively. - connectivity (networkx.Graph): chip connectivity. + 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 (networkx.Graph, optional): chip connectivity. """ - def __init__(self, map, connectivity=None): + def __init__(self, map: Union[list, dict], connectivity: nx.Graph = None): self.connectivity = connectivity self.map = map @@ -102,7 +108,7 @@ def __call__(self, circuit=None): """Return the custom placement if it can be applied to the given circuit (if given). Args: - circuit (qibo.models.Circuit): circuit to be transpiled. + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. Returns: (dict): physical to logical qubit mapping. @@ -124,21 +130,23 @@ def __call__(self, circuit=None): class Subgraph(Placer): """ - Subgraph isomorphism qubit placer, NP-complete it can take a long time - for large circuits. This initialization method may fail for very short circuits. + Subgraph isomorphism qubit placer. + + Since it is a :math:`NP`-complete problem, it can take exponential time for large circuits. + This initialization method may fail for very short circuits. Attributes: - connectivity (networkx.Graph): chip connectivity. + connectivity (:class:`networkx.Graph`): chip connectivity. """ - def __init__(self, connectivity): + def __init__(self, connectivity: nx.Graph): self.connectivity = connectivity def __call__(self, circuit: Circuit): """Find the initial layout of the given circuit using subgraph isomorphism. Args: - circuit (qibo.models.Circuit): circuit to be transpiled. + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. Returns: (dict): physical to logical qubit mapping. @@ -147,7 +155,7 @@ def __call__(self, circuit: Circuit): if len(gates_qubits_pairs) < 3: raise_error( ValueError, - "Circuit must contain at least two two qubit gates to implement subgraph placement.", + "Circuit must contain at least two two-qubit gates to implement subgraph placement.", ) circuit_subgraph = nx.Graph() circuit_subgraph.add_nodes_from(range(self.connectivity.number_of_nodes())) @@ -180,11 +188,12 @@ class Random(Placer): gates can be applied without introducing any SWAP gate. Attributes: - connectivity (networkx.Graph): chip connectivity. - samples (int): number of initial random layouts tested. + connectivity (:class:`networkx.Graph`): chip connectivity. + samples (int, optional): number of initial random layouts tested. + Defaults to :math:`100`. """ - def __init__(self, connectivity, samples=100): + def __init__(self, connectivity, samples: int = 100): self.connectivity = connectivity self.samples = samples @@ -192,10 +201,10 @@ def __call__(self, circuit): """Find an initial layout of the given circuit using random greedy algorithm. Args: - circuit (qibo.models.Circuit): Circuit to be transpiled. + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be transpiled. Returns: - (dict): physical to logical qubit mapping. + (dict): physical-to-logical qubit mapping. """ gates_qubits_pairs = _find_gates_qubits_pairs(circuit) nodes = self.connectivity.number_of_nodes() @@ -218,12 +227,12 @@ def __call__(self, circuit): final_cost = cost return final_mapping - def _cost(self, graph, gates_qubits_pairs): + def _cost(self, graph: nx.Graph, gates_qubits_pairs: list): """ Compute the cost associated to an initial layout as the lengh of the reduced circuit. Args: - graph (networkx.Graph): current hardware qubit mapping. + graph (:class:`networkx.Graph`): current hardware qubit mapping. gates_qubits_pairs (list): circuit representation. Returns: @@ -237,20 +246,32 @@ def _cost(self, graph, gates_qubits_pairs): class ReverseTraversal(Placer): """ - Place qubits based on the algorithm proposed in https://doi.org/10.48550/arXiv.1809.02573. - Works with ShortestPaths routing. + Places qubits based on the algorithm proposed in Reference [1]. - Attributes: - connectivity (networkx.Graph): chip connectivity. - routing_algorithm (qibo.transpiler.abstract.Router): routing algorithm. - depth (int): number of two qubit gates considered before finding initial layout. - if 'None' just one backward step will be implemented. - If depth is greater than the number of two qubit gates in the circuit, the circuit will be routed more than once. - Example: on a circuit with four two qubit gates A-B-C-D using depth = 6, - the routing will be performed on the circuit C-D-D-C-B-A. + Works with ``ShortestPaths`` routing. + + Args: + connectivity (:class:`networkx.Graph`): chip connectivity. + routing_algorithm (:class:`qibo.transpiler.abstract.Router`): routing algorithm. + depth (int, optional): number of two-qubit gates considered before finding initial layout. + If ``None`` just one backward step will be implemented. + If depth is greater than the number of two-qubit gates in the circuit, + the circuit will be routed more than once. + Example: on a circuit with four two-qubit gates :math:`A-B-C-D` + using depth :math:`d = 6`, the routing will be performed + on the circuit :math:`C-D-D-C-B-A`. + + References: + 1. G. Li, Y. Ding, and Y. Xie, *Tackling the Qubit Mapping Problem for NISQ-Era Quantum Devices*. + `arXiv:1809.02573 [cs.ET] `_. """ - def __init__(self, connectivity: nx.Graph, routing_algorithm: Router, depth=None): + def __init__( + self, + connectivity: nx.Graph, + routing_algorithm: Router, + depth: Optional[int] = None, + ): self.connectivity = connectivity self.routing_algorithm = routing_algorithm self.depth = depth @@ -259,7 +280,7 @@ def __call__(self, circuit: Circuit): """Find the initial layout of the given circuit using Reverse Traversal placement. Args: - circuit (qibo.models.Circuit): circuit to be transpiled. + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. Returns: (dict): physical to logical qubit mapping. @@ -273,32 +294,39 @@ def __call__(self, circuit: Circuit): def _assemble_circuit(self, circuit: Circuit): """Assemble a single circuit to apply Reverse Traversal placement based on depth. - Example: for a circuit with four two qubit gates A-B-C-D using depth = 6, - the function will return the circuit C-D-D-C-B-A. + + Example: for a circuit with four two-qubit gates :math:`A-B-C-D` + using depth :math:`d = 6`, the function will return the circuit :math:`C-D-D-C-B-A`. Args: - circuit (qibo.models.Circuit): circuit to be transpiled. + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. Returns: - new_circuit (qibo.models.Circuit): assembled circuit to perform Reverse Traversal placement. + (:class:`qibo.models.circuit.Circuit`): assembled circuit to perform Reverse Traversal placement. """ if self.depth is None: return circuit.invert() + gates_qubits_pairs = _find_gates_qubits_pairs(circuit) circuit_gates = len(gates_qubits_pairs) if circuit_gates == 0: - raise ValueError("The circuit must contain at least a two qubit gate.") + raise_error( + ValueError, "The circuit must contain at least a two-qubit gate." + ) repetitions, remainder = divmod(self.depth, circuit_gates) + assembled_gates_qubits_pairs = [] for _ in range(repetitions): assembled_gates_qubits_pairs += gates_qubits_pairs[:] gates_qubits_pairs.reverse() assembled_gates_qubits_pairs += gates_qubits_pairs[0:remainder] + new_circuit = Circuit(circuit.nqubits) 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): @@ -306,7 +334,7 @@ def _routing_step(self, layout: dict, circuit: Circuit): Args: layout (dict): intial qubit layout. - circuit (qibo.models.Circuit): circuit to be routed. + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be routed. """ _, final_mapping = self.routing_algorithm(circuit, layout) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 46783f74b2..0b37bdfc9e 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -1,7 +1,6 @@ import random from copy import deepcopy -import matplotlib.pyplot as plt import networkx as nx import numpy as np from more_itertools import pairwise diff --git a/src/qibo/transpiler/star_connectivity.py b/src/qibo/transpiler/star_connectivity.py index 7c03aa1d42..3439211be9 100644 --- a/src/qibo/transpiler/star_connectivity.py +++ b/src/qibo/transpiler/star_connectivity.py @@ -5,6 +5,7 @@ class StarConnectivity(Router): """Transforms an arbitrary circuit to one that can be executed on hardware. + This transpiler produces a circuit that respects the following connectivity: q @@ -16,26 +17,24 @@ class StarConnectivity(Router): by adding SWAP gates when needed. Args: - connectivity (networkx.Graph): chip connectivity, not used for this transpiler. - middle_qubit (int): qubit id of the qubit that is in the middle of the star. - verbose (bool): print info messages. + 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. """ - def __init__(self, connectivity=None, middle_qubit=2): + def __init__(self, connectivity=None, middle_qubit: int = 2): self.middle_qubit = middle_qubit def __call__(self, circuit: Circuit, initial_layout=None): """Apply the transpiler transformation on a given circuit. Args: - circuit (qibo.models.Circuit): The original Qibo circuit to transform. + circuit (:class:`qibo.models.circuit.Circuit`): The original Qibo circuit to transform. This circuit must contain up to two-qubit gates. Returns: - new (qibo.models.Circuit): Qibo circuit that performs the same operation - as the original but respects the hardware connectivity. - hardware_qubits (list): List that maps logical to hardware qubits. - This is required for transforming final measurements. + (: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 diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py index 0dfc0b29f2..ec853e78b6 100644 --- a/src/qibo/transpiler/unitary_decompositions.py +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -19,10 +19,10 @@ def u3_decomposition(unitary): """Decomposes arbitrary one-qubit gates to U3. Args: - unitary (np.ndarray): Unitary 2x2 matrix to be decomposed. + unitary (ndarray): Unitary 2x2 matrix to be decomposed. Returns: - theta, phi, lam: parameters of U3 gate. + (float, float, float): parameters of U3 gate. """ # https://github.com/Qiskit/qiskit-terra/blob/d2e3340adb79719f9154b665e8f6d8dc26b3e0aa/qiskit/quantum_info/synthesis/one_qubit_decompose.py#L221 su2 = unitary / np.sqrt(np.linalg.det(unitary)) From 7de2ff66bdbccb60f3f54160fe9ba9634756b887 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 9 Nov 2023 15:38:03 +0400 Subject: [PATCH 43/55] remove dependency on `more-itertools` --- pyproject.toml | 1 - src/qibo/transpiler/router.py | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f99d643412..eb8ec3c736 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,6 @@ joblib = "^1.2.0" matplotlib = "^3.7.0" psutil = "^5.9.4" tabulate = "^0.9.0" -more-itertools = "^9.1.0" [tool.poetry.group.dev] optional = true diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 0b37bdfc9e..2c33e1156b 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -3,7 +3,6 @@ import networkx as nx import numpy as np -from more_itertools import pairwise from qibo import gates from qibo.config import log, raise_error @@ -306,13 +305,13 @@ def _add_swaps(self, path, meeting_point): forward = path[0 : meeting_point + 1] backward = list(reversed(path[meeting_point + 1 :])) if len(forward) > 1: - for f1, f2 in pairwise(forward): + for f1, f2 in zip(forward[:-1], forward[1:]): gate = gates.SWAP(self._qubit_map[f1], self._qubit_map[f2]) self._transpiled_circuit.add(gate) self._added_swaps_list.append(gate) if len(backward) > 1: - for b1, b2 in pairwise(backward): + for b1, b2 in zip(backward[:-1], backward[1:]): gate = gates.SWAP(self._qubit_map[b1], self._qubit_map[b2]) self._transpiled_circuit.add(gate) self._added_swaps_list.append(gate) From 8b1c871909cccbb51e3e006fef3f45367c9002e0 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 9 Nov 2023 16:16:51 +0400 Subject: [PATCH 44/55] improve `router` docstrings --- src/qibo/transpiler/router.py | 167 +++++++++++++++----------------- tests/test_transpiler_router.py | 5 +- 2 files changed, 83 insertions(+), 89 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 2c33e1156b..8470181177 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -15,22 +15,23 @@ 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 + All two-qubit operations can be performed on hardware. Args: - circuit (qibo.models.Circuit): circuit model to check. - connectivity (networkx.graph): chip connectivity. + circuit (:class:`qibo.models.circuit.Circuit`): circuit model to check. + connectivity (:class:`networkx.Graph`): chip connectivity. """ for gate in circuit.queue: if len(gate.qubits) > 2 and not isinstance(gate, gates.M): - raise ConnectivityError(f"{gate.name} acts on more than two qubits.") + raise_error(ConnectivityError, f"{gate.name} acts on more than two qubits.") if len(gate.qubits) == 2: if (gate.qubits[0], gate.qubits[1]) not in connectivity.edges: - raise ConnectivityError( - "Circuit does not respect connectivity. " - f"{gate.name} acts on {gate.qubits}." + raise_error( + ConnectivityError, + f"Circuit does not respect connectivity. {gate.name} acts on {gate.qubits}.", ) @@ -38,26 +39,20 @@ def assert_connectivity(connectivity: nx.Graph, circuit: Circuit): class ShortestPaths(Router): """A class to perform initial qubit mapping and connectivity matching. - Properties: - sampling_split (float): fraction of paths tested (between 0 and 1). - - Attributes: - connectivity (networkx.Graph): chip connectivity. - verbose (bool): print info messages. - initial_layout (dict): initial physical to logical qubit mapping - added_swaps (int): number of swaps added to the circuit to match connectivity. - _gates_qubits_pairs (list): quantum circuit represented as a list (only 2 qubit gates). - _mapping (dict): circuit to physical qubit mapping during transpiling. - _graph (networkx.graph): qubit mapped as nodes of the connectivity graph. - _qubit_map (np.array): circuit to physical qubit mapping during transpiling as vector. - _circuit_position (int): position in the circuit. - + Args: + connectivity (:class:`networkx.Graph`): chip connectivity. + sampling_split (float, optional): fraction of paths tested + (between :math:`0` and :math:`1`). Defaults to :math:`1.0`. + verbose (bool, optional): If ``True``, print info messages. Defaults to ``False``. """ - def __init__(self, connectivity: nx.Graph, sampling_split=1.0, verbose=False): + def __init__( + self, connectivity: nx.Graph, sampling_split: float = 1.0, verbose: bool = False + ): self.connectivity = connectivity self.sampling_split = sampling_split self.verbose = verbose + self.initial_layout = None self._added_swaps = 0 self.final_map = None @@ -70,16 +65,15 @@ def __init__(self, connectivity: nx.Graph, sampling_split=1.0, verbose=False): self._transpiled_circuit = None self._circuit_position = 0 - def __call__(self, circuit: Circuit, initial_layout): + def __call__(self, circuit: Circuit, initial_layout: dict): """Circuit connectivity matching. Args: - circuit (qibo.models.Circuit): circuit to be matched to hardware connectivity. - initial_layout (dict): initial qubit mapping. + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be matched to hardware connectivity. + initial_layout (dict): initial physical-to-logical qubit mapping Returns: - hardware_mapped_circuit (qibo.models.Circuit): circut mapped to hardware topology. - final_mapping (dict): final qubit mapping. + (:class:`qibo.models.circuit.Circuit`, dict): circut mapped to hardware topology, and final qubit mapping. """ self._mapping = initial_layout init_qubit_map = np.asarray(list(initial_layout.values())) @@ -90,6 +84,7 @@ def __call__(self, circuit: Circuit, initial_layout): self._qubit_map = np.sort(init_qubit_map) self._swap_map = deepcopy(init_qubit_map) self._first_transpiler_step(circuit) + while len(self._gates_qubits_pairs) != 0: self._transpiler_step(circuit) hardware_mapped_circuit = self._remap_circuit(np.argsort(init_qubit_map)) @@ -97,6 +92,7 @@ def __call__(self, circuit: Circuit, initial_layout): "q" + str(j): self._swap_map[j] for j in range(self._graph.number_of_nodes()) } + return hardware_mapped_circuit, final_mapping @property @@ -110,7 +106,7 @@ def sampling_split(self): return self._sampling_split @sampling_split.setter - def sampling_split(self, sampling_split): + def sampling_split(self, sampling_split: float): """Set the sampling split, the fraction of possible shortest paths to be analyzed. Args: @@ -122,39 +118,39 @@ def sampling_split(self, sampling_split): else: raise_error(ValueError, "Sampling_split must be in (0:1].") - def _transpiler_step(self, qibo_circuit): + def _transpiler_step(self, circuit: Circuit): """Transpilation step. Find new mapping, add swap gates and apply gates that can be run with this configuration. Args: - qibo_circuit (qibo.models.Circuit): circuit to be transpiled. + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. """ len_before_step = len(self._gates_qubits_pairs) path, meeting_point = self._relocate() self._add_swaps(path, meeting_point) self._update_qubit_map() - self._add_gates(qibo_circuit, len_before_step - len(self._gates_qubits_pairs)) + self._add_gates(circuit, len_before_step - len(self._gates_qubits_pairs)) - def _first_transpiler_step(self, qibo_circuit): + def _first_transpiler_step(self, circuit: Circuit): """First transpilation step. Apply gates that can be run with the initial qubit mapping. Args: - qibo_circuit (qibo.models.Circuit): circuit to be transpiled. + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. """ self._circuit_position = 0 self._added_swaps = 0 self._added_swaps_list = [] len_2q_circuit = len(self._gates_qubits_pairs) self._gates_qubits_pairs = self._reduce(self._graph) - self._add_gates(qibo_circuit, len_2q_circuit - len(self._gates_qubits_pairs)) + self._add_gates(circuit, len_2q_circuit - len(self._gates_qubits_pairs)) - def _reduce(self, graph): - """Reduce the circuit, delete a 2-qubit gate if it can be applied on the current configuration. + def _reduce(self, graph: nx.Graph): + """Reduces the circuit by deleting two-qubit gates if it can be applied on the current configuration. Args: - graph (networkx.Graph): current hardware qubit mapping. + graph (:class:`networkx.Graph`): current hardware qubit mapping. Returns: - new_circuit (list): reduced circuit. + (list): reduced circuit. """ new_circuit = self._gates_qubits_pairs.copy() while ( @@ -164,15 +160,14 @@ def _reduce(self, graph): del new_circuit[0] return new_circuit - def _map_list(self, path): + def _map_list(self, path: list): """Return all possible walks of qubits, or a fraction, for a given path. Args: path (list): path to move qubits. Returns: - mapping_list (list): all possible walks of qubits, or a fraction of them based on self.sampling_split, for a given path. - meeting_point_list (list): qubit meeting point for each path. + (list, list): all possible walks of qubits, or a fraction of them based on self.sampling_split, for a given path, and qubit meeting point for each path. """ path_ends = [path[0], path[-1]] path_middle = path[1:-1] @@ -190,14 +185,14 @@ def _map_list(self, path): mapping = dict(zip(path, values)) mapping_list.append(mapping) meeting_point_list.append(i) + return mapping_list, meeting_point_list def _relocate(self): - """A small greedy algorithm to decide which path to take, and how qubits should walk. + """Greedy algorithm to decide which path to take, and how qubits should walk. Returns: - final_path (list): best path to move qubits. - meeting_point (int): qubit meeting point in the path. + (list, int): best path to move qubits and qubit meeting point in the path. """ nodes = self._graph.number_of_nodes() circuit = self._reduce(self._graph) @@ -230,10 +225,11 @@ def _relocate(self): self._graph = final_graph self._mapping = final_mapping self._gates_qubits_pairs = final_circuit + return final_path, meeting_point - def _initial_checks(self, qubits): - """Initialize the transpiled circuit and check if it can be mapped to the defined connectivity. + def _initial_checks(self, qubits: int): + """Initializes the transpiled circuit and check if it can be mapped to the defined connectivity. Args: qubits (int): number of qubits in the circuit to be transpiled. @@ -255,16 +251,17 @@ def _initial_checks(self, qubits): assert_placement(new_circuit, self._mapping) self._transpiled_circuit = new_circuit - def _add_gates(self, qibo_circuit: Circuit, matched_gates): - """Add one and two qubit gates to transpiled circuit until connectivity is matched. + def _add_gates(self, circuit: Circuit, matched_gates: int): + """Adds one and two qubit gates to transpiled circuit until connectivity is matched. Args: - qibo_circuit (qibo.models.Circuit): circuit to be transpiled. - matched_gates (int): number of two qubit gates that can be applied with the current qubit mapping. + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. + matched_gates (int): number of two-qubit gates that + can be applied with the current qubit mapping. """ index = 0 - while self._circuit_position < len(qibo_circuit.queue): - gate = qibo_circuit.queue[self._circuit_position] + while self._circuit_position < len(circuit.queue): + gate = circuit.queue[self._circuit_position] if isinstance(gate, gates.M): measured_qubits = gate.qubits self._transpiled_circuit.add( @@ -295,8 +292,8 @@ def _add_gates(self, qibo_circuit: Circuit, matched_gates): ) self._circuit_position += 1 - def _add_swaps(self, path, meeting_point): - """Add swaps to the transpiled circuit to move qubits. + def _add_swaps(self, path: list, meeting_point: int): + """Adds swaps to the transpiled circuit to move qubits. Args: path (list): path to move qubits. @@ -317,7 +314,7 @@ def _add_swaps(self, path, meeting_point): self._added_swaps_list.append(gate) def _update_swap_map(self, swap: tuple): - """Update the qubit swap map.""" + """Updates the qubit swap map.""" temp = self._swap_map[swap[0]] self._swap_map[swap[0]] = self._swap_map[swap[1]] self._swap_map[swap[1]] = temp @@ -332,11 +329,10 @@ def _remap_circuit(self, qubit_map): """Map logical to physical qubits in a circuit. Args: - circuit (qibo.models.Circuit): qibo circuit to be remapped. - qubit_map (np.array): new qubit mapping. + qubit_map (ndarray): new qubit mapping. Returns: - new_circuit (qibo.models.Circuit): transpiled circuit mapped with initial qubit mapping. + (:class:`qibo.models.circuit.Circuit`): transpiled circuit mapped with initial qubit mapping. """ new_circuit = Circuit(self._transpiled_circuit.nqubits) for gate in self._transpiled_circuit.queue: @@ -353,15 +349,8 @@ class CircuitMap: this class also implements the initial two qubit blocks decomposition. Args: - initial_layout (dict): initial logical-physical qubit mapping. + initial_layout (dict): initial logical-to-physical qubit mapping. circuit (Circuit): circuit to be routed. - - Attributes: - circuit_blocks (CircuitBlocks): list of two qubit blocks of the circuit. - _physical_logical (list): current logical to physical qubit mapping. - _circuit_logical (list): initial circuit to current logical circuit mapping. - _routed_blocks (CircuitBlocks): current routed circuit blocks. - _swaps (int): number of added swaps. """ def __init__(self, initial_layout: dict, circuit: Circuit): @@ -372,22 +361,22 @@ def __init__(self, initial_layout: dict, circuit: Circuit): self._swaps = 0 def blocks_qubits_pairs(self): - """Return a list containing the qubit pairs of each block.""" + """Returns a list containing the qubit pairs of each block.""" return [block.qubits for block in self.circuit_blocks()] def execute_block(self, block: Block): - """Execute a block by removing it from the circuit representation + """Executes a block by removing it from the circuit representation and adding it to the routed circuit. """ self._routed_blocks.add_block(block.on_qubits(self.get_physical_qubits(block))) self.circuit_blocks.remove_block(block) def routed_circuit(self): - """Return qibo circuit of the routed circuit.""" + """Returns circuit of the routed circuit.""" return self._routed_blocks.circuit() def final_layout(self): - """Return the final physical-circuit qubits mapping.""" + """Returns the final physical-circuit qubits mapping.""" unsorted_dict = { "q" + str(self.circuit_to_physical(i)): i for i in range(len(self._circuit_logical)) @@ -395,7 +384,7 @@ def final_layout(self): return dict(sorted(unsorted_dict.items())) def update(self, swap: tuple): - """Update the logical-physical qubit mapping after applying a SWAP + """Updates the logical-physical qubit mapping after applying a SWAP and add the SWAP gate to the routed blocks, the swap is represented by a tuple containing the logical qubits to be swapped. """ @@ -410,25 +399,25 @@ def update(self, swap: tuple): self._circuit_logical[idx_0], self._circuit_logical[idx_1] = swap[1], swap[0] def get_logical_qubits(self, block: Block): - """Return the current logical qubits where a block is acting""" + """Returns the current logical qubits where a block is acting""" return self.circuit_to_logical(block.qubits) def get_physical_qubits(self, block: Block or int): - """Return the physical qubits where a block is acting.""" + """Returns the physical qubits where a block is acting.""" if isinstance(block, int): block = self.circuit_blocks.search_by_index(block) return self.logical_to_physical(self.get_logical_qubits(block)) def logical_to_physical(self, logical_qubits: tuple): - """Return the physical qubits associated to the logical qubits.""" + """Returns the physical qubits associated to the logical qubits.""" return tuple(self._physical_logical.index(logical_qubits[i]) for i in range(2)) def circuit_to_logical(self, circuit_qubits: tuple): - """Return the current logical qubits associated to the initial circuit qubits.""" + """Returns the current logical qubits associated to the initial circuit qubits.""" return tuple(self._circuit_logical[circuit_qubits[i]] for i in range(2)) def circuit_to_physical(self, circuit_qubit: int): - """Return the current physical qubit associated to an initial circuit qubit.""" + """Returns the current physical qubit associated to an initial circuit qubit.""" return self._physical_logical.index(self._circuit_logical[circuit_qubit]) @@ -438,11 +427,10 @@ def __init__( connectivity: nx.Graph, lookahead: int = 2, decay_lookahead: float = 0.6, - delta=0.001, - seed=42, + delta: float = 0.001, + seed=None, ): - """Routing algorithm proposed in - https://doi.org/10.48550/arXiv.1809.02573 + """Routing algorithm proposed in Ref [1]. Args: connectivity (dict): hardware chip connectivity. @@ -452,6 +440,10 @@ def __init__( delta (float): this parameter defines the number of swaps vs depth trade-off by deciding how the algorithm tends to select non-overlapping SWAPs. seed (int): seed for the candidate random choice as tiebraker. + + References: + 1. G. Li, Y. Ding, and Y. Xie, *Tackling the Qubit Mapping Problem for NISQ-Era Quantum Devices*. + `arXiv:1809.02573 [cs.ET] `_. """ self.connectivity = connectivity self.lookahead = lookahead @@ -465,15 +457,15 @@ def __init__( self._memory_map = None random.seed(seed) - def __call__(self, circuit, initial_layout): + def __call__(self, circuit: Circuit, initial_layout: dict): """Route the circuit. Args: - circuit (qibo.models.Circuit): circuit to be routed. + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be routed. initial_layout (dict): initial physical to logical qubit mapping. Returns: - (qibo.models.Circuit): routed circuit. + (:class:`qibo.models.circuit.Circuit`, dict): routed circuit and final layout. """ self._preprocessing(circuit=circuit, initial_layout=initial_layout) while self._dag.number_of_nodes() != 0: @@ -482,6 +474,7 @@ def __call__(self, circuit, initial_layout): self._execute_blocks(execute_block_list) else: self._find_new_mapping() + return self.circuit.routed_circuit(), self.circuit.final_layout() @property @@ -489,7 +482,7 @@ def added_swaps(self): """Number of SWAP gates added to the circuit during routing.""" return self.circuit._swaps - def _preprocessing(self, circuit: Circuit, initial_layout): + def _preprocessing(self, circuit: Circuit, initial_layout: dict): """The following objects will be initialised: - circuit: class to represent circuit and to perform logical-physical qubit mapping. - _dist_matrix: matrix reporting the shortest path lengh between all node pairs. @@ -517,7 +510,7 @@ def _update_front_layer(self): self._front_layer = self._get_dag_layer(0) def _get_dag_layer(self, n_layer): - """Return the n topological layer of the dag.""" + """Return the :math:`n`-topological layer of the dag.""" return [node[0] for node in self._dag.nodes(data="layer") if node[1] == n_layer] def _find_new_mapping(self): @@ -582,7 +575,7 @@ def _check_execution(self): """Check if some gatesblocks in the front layer can be executed in the current configuration. Returns: - list of executable blocks if there are, None otherwise. + (list): executable blocks if there are, ``None`` otherwise. """ executable_blocks = [] for block in self._front_layer: @@ -621,7 +614,7 @@ def _create_dag(gates_qubits_pairs): gates_qubits_pairs (list): list of qubits tuples where gates/blocks acts. Returns: - (nx.DiGraph): dag of the circuit. + (:class:`networkx.DiGraph`): adjoint of the circuit. """ dag = nx.DiGraph() dag.add_nodes_from(range(len(gates_qubits_pairs))) diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 3a5b8f50c4..146e11b849 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -276,12 +276,13 @@ def test_sabre_matched(): ) -def test_sabre_simple(): +@pytest.mark.parametrize("seed", [42]) +def test_sabre_simple(seed): placer = Trivial() circ = Circuit(5) circ.add(gates.CZ(0, 1)) initial_layout = placer(circ) - router = Sabre(connectivity=star_connectivity()) + router = Sabre(connectivity=star_connectivity(), seed=seed) routed_circuit, final_map = router(circuit=circ, initial_layout=initial_layout) assert router.added_swaps == 1 assert final_map == {"q0": 2, "q1": 1, "q2": 0, "q3": 3, "q4": 4} From 643a9686b209ad92154819c000efa47bb65c8b18 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 10 Nov 2023 09:15:55 +0400 Subject: [PATCH 45/55] final docstring changes --- src/qibo/transpiler/unitary_decompositions.py | 33 +++++++++++----- src/qibo/transpiler/unroller.py | 39 ++++++++++++------- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py index ec853e78b6..e737ba649e 100644 --- a/src/qibo/transpiler/unitary_decompositions.py +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -19,7 +19,7 @@ def u3_decomposition(unitary): """Decomposes arbitrary one-qubit gates to U3. Args: - unitary (ndarray): Unitary 2x2 matrix to be decomposed. + unitary (ndarray): Unitary :math:`2 \\times 2` matrix to be decomposed. Returns: (float, float, float): parameters of U3 gate. @@ -31,6 +31,7 @@ def u3_decomposition(unitary): minus = np.angle(su2[1, 0]) phi = plus + minus lam = plus - minus + return theta, phi, lam @@ -40,11 +41,14 @@ def calculate_psi(unitary, magic_basis=magic_basis, backend=None): See step (1) of Appendix A in arXiv:quant-ph/0011050. Args: - unitary (np.ndarray): Unitary matrix of the gate we are - decomposing in the computational basis. + unitary (ndarray): Unitary matrix of the gate we are decomposing + in the computational basis. + magic_basis (ndarray, optional): basis in which to solve the eigenvalue problem. + Defaults to ``magic basis``. + backend (:class:`qibo.backends.abstract.Backend`): Backend to use for calculations. Returns: - Eigenvectors (in the computational basis) and eigenvalues of :math:`U^{T} U`. + (ndarray) Eigenvectors in the computational basis and eigenvalues of :math:`U^{T} U`. """ if backend is None: # pragma: no cover backend = GlobalBackend() @@ -74,7 +78,15 @@ def calculate_psi(unitary, magic_basis=magic_basis, backend=None): def schmidt_decompose(state): - """Decomposes a two-qubit product state to its single-qubit parts.""" + """Decomposes a two-qubit product state to its single-qubit parts. + + Args: + state (ndarray): product state to be decomposed. + + Returns: + (ndarray, ndarray): decomposition + + """ u, d, v = np.linalg.svd(np.reshape(state, (2, 2))) if not np.allclose(d, [1, 0]): # pragma: no cover raise_error( @@ -90,10 +102,10 @@ def calculate_single_qubit_unitaries(psi): See Lemma 1 of Appendix A in arXiv:quant-ph/0011050. Args: - psi (np.ndarray): Maximally entangled two-qubit states that define a basis. + psi (ndarray): Maximally entangled two-qubit states that define a basis. Returns: - Local unitaries UA and UB that map the given basis to the magic basis. + (ndarray, ndarray): Local unitaries UA and UB that map the given basis to the magic basis. """ # TODO: Handle the case where psi is not real in the magic basis @@ -229,11 +241,12 @@ def two_qubit_decomposition(q0, q1, unitary, backend=None): """Performs two qubit unitary gate decomposition (24) from arXiv:quant-ph/0307177. Args: - q0, q1 (int): Target qubits - unitary (np.ndarray): Unitary 4x4 matrix we are decomposing. + q0 (int): index of the first qubit. + q1 (int): index of the second qubit. + unitary (ndarray): Unitary :math:`4 \\times 4` to be decomposed. Returns: - list of gates implementing decomposition (24) from arXiv:quant-ph/0307177 + (list): gates implementing decomposition (24) from arXiv:quant-ph/0307177 """ if backend is None: # pragma: no cover backend = GlobalBackend() diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index e7ba8ce928..74f42ace99 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -19,12 +19,14 @@ class NativeGates(Unroller): """Translates a circuit to native gates. Args: - circuit (qibo.models.Circuit): circuit model to translate into native gates. - single_qubit_natives (Tuple): single qubit native gates. - two_qubit_natives (NativeType): two qubit native gates supported by the quantum hardware. + circuit (:class:`qibo.models.circuit.Circuit`): circuit model to translate + into native gates. + single_qubit_natives (tuple): single qubit native gates. + two_qubit_natives (:class:`qibo.transpiler.abstract.NativeType`): two-qubit native gates + supported by the quantum hardware. Returns: - translated_circuit (qibo.models.Circuit): equivalent circuit with native gates. + (:class:`qibo.models.circuit.Circuit`): equivalent circuit with native gates. """ def __init__( @@ -66,38 +68,45 @@ def assert_decomposition( """Checks if a circuit has been correctly decmposed into native gates. Args: - circuit (qibo.models.Circuit): circuit model to check. + circuit (:class:`qibo.models.circuit.Circuit`): circuit model to check. """ for gate in circuit.queue: if isinstance(gate, gates.M): continue if len(gate.qubits) == 1: if not isinstance(gate, single_qubit_natives): - raise DecompositionError( - f"{gate.name} is not a single qubit native gate." + raise_error( + DecompositionError, + f"{gate.name} is not a single qubit native gate.", ) elif len(gate.qubits) == 2: try: native_type_gate = NativeType.from_gate(gate) if not (native_type_gate in two_qubit_natives): - raise DecompositionError( - f"{gate.name} is not a two qubit native gate." + raise_error( + DecompositionError, + f"{gate.name} is not a two qubit native gate.", ) except ValueError: - raise DecompositionError(f"{gate.name} is not a two qubit native gate.") + raise_error( + DecompositionError, f"{gate.name} is not a two qubit native gate." + ) else: - raise DecompositionError(f"{gate.name} acts on more than two qubits.") + raise_error( + DecompositionError, f"{gate.name} acts on more than two qubits." + ) def translate_gate(gate, native_gates: NativeType): - """Maps Qibo gates to a hardware native implementation. + """Maps gates to a hardware-native implementation. Args: - gate (qibo.gates.abstract.Gate): gate to be decomposed. - native_gates (NativeType): two qubit native gates supported by the quantum hardware. + gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. + native_gates (:class:`qibo.transpiler.abstract.NativeType`): + two-qubit native gates supported by the quantum hardware. Returns: - List of native gates + (list): List of native gates """ if isinstance(gate, (gates.M, gates.I, gates.Align)): return gate From 938061122b97c95b54866737eab211717d630a8b Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 10 Nov 2023 09:29:55 +0400 Subject: [PATCH 46/55] fix doc example --- doc/source/code-examples/advancedexamples.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index c3378304ad..909a8a93c1 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -2003,6 +2003,7 @@ is possible to use CZ and/or iSWAP. When both CZ and iSWAP gates are available t one that minimizes the use of two-qubit gates. Multiple transpilation steps can be implemented using the :class:`qibo.transpiler.pipeline.Pipeline`: + .. testcode:: python import networkx as nx @@ -2054,7 +2055,7 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile connectivity=star_connectivity(), initial_layout=initial_layout, final_layout=final_layout, - native_gates=NativeType.iSWAP, + native_gates=NativeType.iSWAP ) In this case circuits will first be transpiled to respect the 5-qubit star connectivity, with qubit 2 as the middle qubit. This will potentially add some SWAP gates. From d58c3ad1759a069c9f44fa4aa7998809ce7f4396 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 10 Nov 2023 12:21:59 +0400 Subject: [PATCH 47/55] fix example --- 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 909a8a93c1..429a0ef732 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -2008,7 +2008,7 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile import networkx as nx - from qibo import Gates + from qibo import gates from qibo.models import Circuit from qibo.transpiler.pipeline import Passes, assert_transpiling from qibo.transpiler.abstract import NativeType From e26d30ec2a0e036ec601bf2149e211bb30a69855 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 10 Nov 2023 13:16:08 +0400 Subject: [PATCH 48/55] fix example --- 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 429a0ef732..15d8d5f1ad 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -2050,7 +2050,7 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile # Optinally call assert_transpiling to check that the final circuit can be executed on hardware assert_transpiling( - original_circuit=circ, + original_circuit=circuit, transpiled_circuit=transpiled_circ, connectivity=star_connectivity(), initial_layout=initial_layout, From f0392ae0da02cf10660d322fe1ffef68a9ad1504 Mon Sep 17 00:00:00 2001 From: Simone Bordoni <101353773+Simone-Bordoni@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:23:12 +0400 Subject: [PATCH 49/55] Update src/qibo/transpiler/placer.py Co-authored-by: BrunoLiegiBastonLiegi <45011234+BrunoLiegiBastonLiegi@users.noreply.github.com> --- src/qibo/transpiler/placer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 1aa9c671e9..cb9ed21a2b 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -248,7 +248,7 @@ class ReverseTraversal(Placer): """ Places qubits based on the algorithm proposed in Reference [1]. - Works with ``ShortestPaths`` routing. + Compatible with all the available ``Router``s. Args: connectivity (:class:`networkx.Graph`): chip connectivity. From 21eb726435f8026300cdfce9596fc1678c9948ff Mon Sep 17 00:00:00 2001 From: Simone Bordoni <101353773+Simone-Bordoni@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:23:44 +0400 Subject: [PATCH 50/55] Update src/qibo/transpiler/router.py Co-authored-by: BrunoLiegiBastonLiegi <45011234+BrunoLiegiBastonLiegi@users.noreply.github.com> --- src/qibo/transpiler/router.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 8470181177..1004bdfcbf 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -556,8 +556,7 @@ def _swap_candidates(self): """ candidates = [] for block in self._front_layer: - qubits = self.circuit.get_physical_qubits(block) - for qubit in qubits: + for qubit in self.circuit.get_physical_qubits(block) for connected in self.connectivity.neighbors(qubit): candidate = tuple( sorted( From 2ba6dc630d06b919c112c07cb387f7f647bca540 Mon Sep 17 00:00:00 2001 From: Simone Bordoni <101353773+Simone-Bordoni@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:24:09 +0400 Subject: [PATCH 51/55] Update src/qibo/transpiler/router.py Co-authored-by: BrunoLiegiBastonLiegi <45011234+BrunoLiegiBastonLiegi@users.noreply.github.com> --- src/qibo/transpiler/router.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 1004bdfcbf..6eb52f2356 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -578,9 +578,8 @@ def _check_execution(self): """ executable_blocks = [] for block in self._front_layer: - qubits = self.circuit.get_physical_qubits(block) if ( - qubits in self.connectivity.edges + self.circuit.get_physical_qubits(block) in self.connectivity.edges or not self.circuit.circuit_blocks.search_by_index(block).entangled ): executable_blocks.append(block) From bbbd0aa48c06dd78b14191cca8bea5d59322d360 Mon Sep 17 00:00:00 2001 From: Simone Bordoni <101353773+Simone-Bordoni@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:24:28 +0400 Subject: [PATCH 52/55] Update src/qibo/transpiler/pipeline.py Co-authored-by: BrunoLiegiBastonLiegi <45011234+BrunoLiegiBastonLiegi@users.noreply.github.com> --- src/qibo/transpiler/pipeline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index 3813388559..cf428c8ed4 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -163,7 +163,6 @@ def default(self, connectivity: nx.Graph): # default_passes.append(ReverseTraversal(connectivity=connectivity, routing_algorithm=ShortestPaths(connectivity), depth=5)) # default router pass default_passes.append(StarConnectivity()) - # default_passes.append(ShortestPaths(connectivity=connectivity)) # default unroller pass default_passes.append(NativeGates(two_qubit_natives=self.native_gates)) return default_passes From 778c9ac2d076451ebcda7552bd515b42a66e2e36 Mon Sep 17 00:00:00 2001 From: Simone Bordoni <101353773+Simone-Bordoni@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:24:40 +0400 Subject: [PATCH 53/55] Update src/qibo/transpiler/pipeline.py Co-authored-by: BrunoLiegiBastonLiegi <45011234+BrunoLiegiBastonLiegi@users.noreply.github.com> --- src/qibo/transpiler/pipeline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index cf428c8ed4..ecc1b8b687 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -160,7 +160,6 @@ def default(self, connectivity: nx.Graph): default_passes.append(Preprocessing(connectivity=connectivity)) # default placer pass default_passes.append(Trivial(connectivity=connectivity)) - # default_passes.append(ReverseTraversal(connectivity=connectivity, routing_algorithm=ShortestPaths(connectivity), depth=5)) # default router pass default_passes.append(StarConnectivity()) # default unroller pass From 486dd6d8d4d62c34e45e83fde16ac32e170e07f9 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Fri, 10 Nov 2023 14:29:21 +0400 Subject: [PATCH 54/55] corrections by Andrea and fixed tests --- src/qibo/transpiler/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 6eb52f2356..ebc6571252 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -556,7 +556,7 @@ def _swap_candidates(self): """ candidates = [] for block in self._front_layer: - for qubit in self.circuit.get_physical_qubits(block) + for qubit in self.circuit.get_physical_qubits(block): for connected in self.connectivity.neighbors(qubit): candidate = tuple( sorted( From 73bbeb3222fdead8e4dd479ec67e2a9a605b36d4 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Fri, 10 Nov 2023 18:46:33 +0400 Subject: [PATCH 55/55] fix examples tests --- doc/source/code-examples/advancedexamples.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index 15d8d5f1ad..2adbae6128 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -2049,6 +2049,8 @@ 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,