Skip to content

Commit

Permalink
Merge pull request #1369 from qiboteam/fix_transpiler_bugs
Browse files Browse the repository at this point in the history
Fix transpiler bugs about moved measurements and duplicated gates
  • Loading branch information
renatomello authored Jun 28, 2024
2 parents 88825fa + d78b626 commit 9161904
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 130 deletions.
2 changes: 1 addition & 1 deletion src/qibo/models/error_mitigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1063,7 +1063,7 @@ def _execute_circuit(circuit, qubit_map, noise_model=None, nshots=10000, backend
backend = GlobalBackend()
elif backend.name == "qibolab": # pragma: no cover
backend.transpiler.passes[1] = Custom(
map=qubit_map, connectivity=backend.platform.topology
initial_map=qubit_map, connectivity=backend.platform.topology
)
elif noise_model is not None:
circuit = noise_model.apply(circuit)
Expand Down
5 changes: 2 additions & 3 deletions src/qibo/transpiler/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def __call__(self, circuit):
physical (keys) to logical (values) qubit. If `int_qubit_name` is `True`
each key `i` correspond to the `i-th` qubit in the graph.
"""
self.initial_layout = None
final_layout = self.initial_layout = None
for transpiler_pass in self.passes:
if isinstance(transpiler_pass, Optimizer):
transpiler_pass.connectivity = self.connectivity
Expand Down Expand Up @@ -259,8 +259,7 @@ def __call__(self, circuit):
TranspilerPipelineError,
f"Unrecognised transpiler pass: {transpiler_pass}",
)
# TODO: use directly integers keys
if self.int_qubit_names:
if self.int_qubit_names and final_layout is not None:
final_layout = {int(key[1:]): value for key, value in final_layout.items()}
return circuit, final_layout

Expand Down
78 changes: 51 additions & 27 deletions src/qibo/transpiler/placer.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import random
from typing import Optional, Union

import networkx as nx

from qibo import gates
from qibo.backends import _check_backend_and_local_state
from qibo.config import log, raise_error
from qibo.models import Circuit
from qibo.transpiler._exceptions import PlacementError
Expand Down Expand Up @@ -32,7 +32,8 @@ def assert_placement(
if circuit.nqubits < len(layout):
raise_error(
PlacementError,
"Layout can't be used on circuit. Ancillary extra qubits need to be added to the circuit.",
"Layout can't be used on circuit. "
+ "Ancillary extra qubits need to be added to the circuit.",
)


Expand All @@ -46,11 +47,14 @@ def assert_mapping_consistency(layout: dict, connectivity: nx.Graph = None):
qubits of the original connectivity graph. Defaults to ``None``.
"""
values = sorted(layout.values())
if connectivity is not None:
ref_keys = ["q" + str(i) for i in list(connectivity.nodes)]
else:
ref_keys = ["q" + str(i) for i in range(len(values))]
if list(layout.keys()) != ref_keys:
physical_qubits = list(layout)
nodes = (
list(range(len(values))) if connectivity is None else list(connectivity.nodes)
)
ref_keys = (
["q" + str(i) for i in nodes] if isinstance(physical_qubits[0], str) else nodes
)
if physical_qubits != ref_keys:
raise_error(
PlacementError,
"Some physical qubits in the layout may be missing or duplicated.",
Expand Down Expand Up @@ -115,7 +119,7 @@ def __call__(self, circuit: Circuit):
Only single qubit gates and two qubits gates are supported by the router.
Returns:
(dict): physical to logical qubit mapping.
dict: physical to logical qubit mapping.
"""

# find the number of qubits for hardware circuit
Expand Down Expand Up @@ -204,9 +208,9 @@ class Custom(Placer):
qubits of the original connectivity graph. Defaults to ``None``.
"""

def __init__(self, map: Union[list, dict], connectivity: nx.Graph = None):
def __init__(self, initial_map: Union[list, dict], connectivity: nx.Graph = None):
self.connectivity = connectivity
self.map = map
self.initial_map = initial_map

def __call__(self, circuit=None):
"""Return the custom placement if it can be applied to the given circuit (if given).
Expand All @@ -217,24 +221,30 @@ def __call__(self, circuit=None):
Returns:
(dict): physical to logical qubit mapping.
"""
if isinstance(self.map, dict):
if isinstance(self.initial_map, dict):
pass
elif isinstance(self.map, list):
elif isinstance(self.initial_map, list):
if self.connectivity is not None:
self.map = dict(
zip(["q" + str(i) for i in self.connectivity.nodes()], self.map)
self.initial_map = dict(
zip(
["q" + str(i) for i in self.connectivity.nodes()],
self.initial_map,
)
)
else:
self.map = dict(
zip(["q" + str(i) for i in range(len(self.map))], self.map)
self.initial_map = dict(
zip(
["q" + str(i) for i in range(len(self.initial_map))],
self.initial_map,
)
)
else:
raise_error(TypeError, "Use dict or list to define mapping.")
if circuit is not None:
assert_placement(circuit, self.map, connectivity=self.connectivity)
assert_placement(circuit, self.initial_map, connectivity=self.connectivity)
else:
assert_mapping_consistency(self.map, connectivity=self.connectivity)
return self.map
assert_mapping_consistency(self.initial_map, connectivity=self.connectivity)
return self.initial_map


class Subgraph(Placer):
Expand Down Expand Up @@ -265,7 +275,8 @@ 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(list(range(circuit.nqubits)))
Expand All @@ -274,7 +285,7 @@ def __call__(self, circuit: Circuit):
)
i = 0
circuit_subgraph.add_edge(gates_qubits_pairs[i][0], gates_qubits_pairs[i][1])
while matcher.subgraph_is_monomorphic() == True:
while matcher.subgraph_is_monomorphic():
result = matcher
i += 1
circuit_subgraph.add_edge(
Expand Down Expand Up @@ -304,11 +315,15 @@ class Random(Placer):
connectivity (:class:`networkx.Graph`): chip connectivity.
samples (int, optional): number of initial random layouts tested.
Defaults to :math:`100`.
seed (int or :class:`numpy.random.Generator`, optional): Either a generator of
random numbers or a fixed seed to initialize a generator. If ``None``,
initializes a generator with a random seed. Defaults to ``None``.
"""

def __init__(self, connectivity, samples: int = 100):
def __init__(self, connectivity, samples: int = 100, seed=None):
self.connectivity = connectivity
self.samples = samples
self.seed = seed

def __call__(self, circuit):
"""Find an initial layout of the given circuit using random greedy algorithm.
Expand All @@ -319,6 +334,7 @@ def __call__(self, circuit):
Returns:
(dict): physical-to-logical qubit mapping.
"""
_, local_state = _check_backend_and_local_state(self.seed, backend=None)
gates_qubits_pairs = _find_gates_qubits_pairs(circuit)
nodes = self.connectivity.number_of_nodes()
keys = list(self.connectivity.nodes())
Expand All @@ -328,17 +344,23 @@ def __call__(self, circuit):
final_graph = nx.relabel_nodes(self.connectivity, 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)))
mapping = dict(
zip(keys, local_state.choice(range(nodes), nodes, replace=False))
)
graph = nx.relabel_nodes(self.connectivity, mapping)
cost = self._cost(graph, gates_qubits_pairs)

if cost == 0:
return dict(zip(dict_keys, list(mapping.values())))
final_layout = dict(zip(dict_keys, list(mapping.values())))
return dict(sorted(final_layout.items()))

if cost < final_cost:
final_graph = graph
final_mapping = mapping
final_cost = cost

return dict(zip(dict_keys, list(final_mapping.values())))
final_layout = dict(zip(dict_keys, list(final_mapping.values())))
return dict(sorted(final_layout.items()))

def _cost(self, graph: nx.Graph, gates_qubits_pairs: list):
"""
Expand Down Expand Up @@ -376,7 +398,8 @@ class ReverseTraversal(Placer):
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*.
1. G. Li, Y. Ding, and Y. Xie,
*Tackling the Qubit Mapping Problem for NISQ-Era Quantum Devices*.
`arXiv:1809.02573 [cs.ET] <https://arxiv.org/abs/1809.02573>`_.
"""

Expand Down Expand Up @@ -416,7 +439,8 @@ def _assemble_circuit(self, circuit: Circuit):
circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled.
Returns:
(:class:`qibo.models.circuit.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:
Expand Down
Loading

0 comments on commit 9161904

Please sign in to comment.