import networkx as nx
from qibo.backends import _check_backend
from qibo.config import raise_error
from qibo.models import Circuit
from qibo.transpiler._exceptions import (
ConnectivityError,
PlacementError,
TranspilerPipelineError,
)
from qibo.transpiler.abstract import Optimizer, Placer, Router
from qibo.transpiler.asserts import (
assert_connectivity,
assert_decomposition,
assert_placement,
)
from qibo.transpiler.unroller import DecompositionError, NativeGates, Unroller
[docs]def restrict_connectivity_qubits(connectivity: nx.Graph, qubits: list[str]):
"""Restrict the connectivity to selected qubits.
Args:
connectivity (:class:`networkx.Graph`): Hardware connectivity.
qubits (list): List of qubits to select.
Returns:
(:class:`networkx.Graph`): New restricted connectivity.
"""
if not set(qubits).issubset(set(connectivity.nodes)):
raise_error(
ConnectivityError, "Some qubits are not in the original connectivity."
)
new_connectivity = nx.Graph()
new_connectivity.add_nodes_from(qubits)
new_edges = [
edge for edge in connectivity.edges if edge[0] in qubits and edge[1] in qubits
]
new_connectivity.add_edges_from(new_edges)
if not nx.is_connected(new_connectivity):
raise_error(ConnectivityError, "New connectivity graph is not connected.")
return new_connectivity
[docs]class Passes:
"""Define a transpiler pipeline consisting of smaller transpiler steps that are applied sequentially:
Args:
passes (list, optional): List of transpiler passes to be applied sequentially.
If ``None``, default transpiler will be used. Defaults to ``None``.
connectivity (:class:`networkx.Graph`, optional): Hardware connectivity.
native_gates (:class:`qibo.transpiler.unroller.NativeGates`, optional): Native gates supported by the hardware.
Defaults to :class:`qibo.transpiler.unroller.NativeGates.default()`.
on_qubits (list, optional): List of qubits to be used in the transpiler.
If ``None``, all qubits in the connectivity will be used. Defaults to ``None``.
"""
def __init__(
self,
passes: list = None,
connectivity: nx.Graph = None,
native_gates: NativeGates = NativeGates.default(),
on_qubits: list = None,
):
if on_qubits is not None:
connectivity = restrict_connectivity_qubits(connectivity, on_qubits)
self.connectivity = connectivity
self.native_gates = native_gates
self.passes = [] if passes is None else passes
def __call__(self, circuit, backend=None): # , backend=None):
"""Apply the transpiler pipeline to the circuit.
Args:
circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be transpiled.
Returns:
(:class:`qibo.models.circuit.Circuit`, dict): Transpiled circuit and final {logical: physical} qubit mapping.
"""
backend = _check_backend(backend)
final_layout = None
for transpiler_pass in self.passes:
if isinstance(transpiler_pass, Optimizer):
transpiler_pass.connectivity = self.connectivity
circuit = transpiler_pass(circuit)
elif isinstance(transpiler_pass, Placer):
transpiler_pass.connectivity = self.connectivity
final_layout = transpiler_pass(circuit, backend=backend)
elif isinstance(transpiler_pass, Router):
transpiler_pass.connectivity = self.connectivity
circuit, final_layout = transpiler_pass(circuit)
elif isinstance(transpiler_pass, Unroller):
circuit = transpiler_pass(circuit)
elif transpiler_pass.__class__.__name__ == "QiskitPasses":
circuit = transpiler_pass(circuit)
else:
raise_error(
TranspilerPipelineError,
f"Unrecognised transpiler pass: {transpiler_pass}",
)
return circuit, final_layout
[docs] def is_satisfied(self, circuit: Circuit):
"""Check if the circuit respects the hardware connectivity and native gates.
Args:
circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be checked.
Returns:
(bool): ``True`` if the circuit respects the hardware connectivity and native gates, ``False`` otherwise.
"""
try:
assert_placement(circuit=circuit, connectivity=self.connectivity)
assert_connectivity(circuit=circuit, connectivity=self.connectivity)
assert_decomposition(circuit=circuit, native_gates=self.native_gates)
return True
except (ConnectivityError, DecompositionError, PlacementError, ValueError):
return False