Skip to content
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
62100c1
added abstract initial mapper and identity initial mapper
ammareltigani Aug 15, 2022
9df051b
added __str__ and __repr__ for MappingManager
ammareltigani Aug 15, 2022
8610ae2
minor bug
ammareltigani Aug 15, 2022
d9a7a3c
made MappingManager not serializable
ammareltigani Aug 16, 2022
c774674
removed unused import
ammareltigani Aug 16, 2022
a06c240
merging with mapping-manager repr and str PR #5828
ammareltigani Aug 16, 2022
c12cc03
pushed AbstractInitialMapping and IdentityInitialMapping name to 'cir…
ammareltigani Aug 16, 2022
83e976f
minor lint fix
ammareltigani Aug 16, 2022
24acc3d
addressed comments
ammareltigani Aug 16, 2022
2e1cec7
Merge branch 'add-str-and-repr-to-mapping_manager' into routing-initi…
ammareltigani Aug 16, 2022
79061de
addressed comments
ammareltigani Aug 16, 2022
dfe80a9
fixed bug with edges not being sorted for graph equality testing
ammareltigani Aug 16, 2022
cb798dc
Merge branch 'add-str-and-repr-to-mapping_manager' into routing-initi…
ammareltigani Aug 16, 2022
6ee60a9
fixed bug with digraphs repr method in MappingManager and added test …
ammareltigani Aug 16, 2022
0c6de8b
Merge branch 'add-str-and-repr-to-mapping_manager' into routing-initi…
ammareltigani Aug 16, 2022
ecadfdb
addressed some comments
ammareltigani Aug 16, 2022
7819263
added grid testing device
ammareltigani Aug 17, 2022
5dd4577
added grid routing testing device
ammareltigani Aug 16, 2022
cc79064
formatting
ammareltigani Aug 16, 2022
fecc4b0
added line_initial_mapper and some tests; needs more testing
ammareltigani Aug 16, 2022
b3a1445
merged with device setup
ammareltigani Aug 17, 2022
9bcf54f
formatting
ammareltigani Aug 17, 2022
f4dae72
formatting
ammareltigani Aug 17, 2022
9ecb52a
changed interface for LineInitialMapper and added better tests; test …
ammareltigani Aug 17, 2022
719b284
addressed comments and added ring device
ammareltigani Aug 17, 2022
8cbbe8f
Merge branch 'routing-initial_mapping_device_setup' into routing-line…
ammareltigani Aug 17, 2022
735cba8
added test for supportin directed graphs
ammareltigani Aug 17, 2022
9ba9cef
changed interface for AbstractInitialMapper
ammareltigani Aug 17, 2022
211bb2d
Merge branch 'routing-initial_mapping_setup' into routing-line_initia…
ammareltigani Aug 17, 2022
82cdbc0
formatting
ammareltigani Aug 17, 2022
632dfe6
Merge branch 'master' into routing-initial_mapping_device_setup
ammareltigani Aug 19, 2022
fca9052
changed RoutingTestingDevice interface; need to change is_isomorphic …
ammareltigani Aug 19, 2022
34ef897
added hard-coded isomorphism tests
ammareltigani Aug 19, 2022
67546d8
fixed type issue
ammareltigani Aug 19, 2022
e120be2
Merge branch 'master' into routing-line_initial_mapper
ammareltigani Aug 19, 2022
0d2345c
removed redundant imports
ammareltigani Aug 19, 2022
209cc35
Merge branch 'routing-initial_mapping_device_setup' into routing-line…
ammareltigani Aug 19, 2022
2544069
merged with routing testing device PR #5830
ammareltigani Aug 19, 2022
4493826
simplified _value_equalit_values_
ammareltigani Aug 19, 2022
8827b91
addressed comments
ammareltigani Aug 19, 2022
70de81e
Merge branch 'routing-initial_mapping_device_setup' into routing-line…
ammareltigani Aug 19, 2022
60ed0ac
removed unused import
ammareltigani Aug 19, 2022
eefc089
Merge branch 'routing-initial_mapping_device_setup' into routing-line…
ammareltigani Aug 19, 2022
c7506bd
fixed nits
ammareltigani Aug 20, 2022
d92f515
Merge branch 'routing-initial_mapping_device_setup' into routing-line…
ammareltigani Aug 20, 2022
d41cedb
Merge branch 'master' into routing-line_initial_mapper
tanujkhattar Aug 20, 2022
7509aa9
addressed comments
ammareltigani Aug 23, 2022
c21eae2
formatting
ammareltigani Aug 23, 2022
854e867
Merge branch 'routing-line_initial_mapper' of https://github.com/amma…
ammareltigani Aug 23, 2022
bad20d3
small fixes
ammareltigani Aug 23, 2022
26f14bd
removed unused import
ammareltigani Aug 23, 2022
4170236
modified test file
ammareltigani Aug 23, 2022
6bed99a
debugging
ammareltigani Aug 23, 2022
5f4b848
removed print statements
ammareltigani Aug 23, 2022
e1de30a
debugging statement
ammareltigani Aug 23, 2022
3237bd7
debugging statement
ammareltigani Aug 24, 2022
64f6ba8
fix
ammareltigani Aug 24, 2022
a2d8d2d
fix
ammareltigani Aug 24, 2022
1b8c315
print statement
ammareltigani Aug 24, 2022
06ab64e
edges sorting
ammareltigani Aug 24, 2022
80b1f0e
addressed comments; ready for review
ammareltigani Aug 24, 2022
0f1454d
fixed type bug
ammareltigani Aug 24, 2022
7c4ebbd
cleanup
ammareltigani Aug 24, 2022
9835eb1
ready for review
ammareltigani Aug 25, 2022
c72de13
type and lint fixes
ammareltigani Aug 25, 2022
2ecb278
slightly modified _make_circuit_graph()
ammareltigani Aug 25, 2022
54e96ca
Merge branch 'master' into routing-line_initial_mapper
tanujkhattar Aug 25, 2022
6989acc
added test for testing valid circuits and fixed bug in _make_circuit_…
ammareltigani Aug 25, 2022
6c8a661
Merge branch 'routing-line_initial_mapper' of https://github.com/amma…
ammareltigani Aug 25, 2022
b6c4bb3
Merge branch 'master' into routing-line_initial_mapper
ammareltigani Aug 26, 2022
cfe8704
fixed nits
ammareltigani Aug 27, 2022
c69a0db
Merge branch 'routing-line_initial_mapper' of https://github.com/amma…
ammareltigani Aug 27, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cirq-core/cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@
expand_composite,
HardCodedInitialMapper,
is_negligible_turn,
LineInitialMapper,
MappingManager,
map_moments,
map_operations,
Expand Down
1 change: 1 addition & 0 deletions cirq-core/cirq/protocols/json_test_data/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
# Routing utilities
'HardCodedInitialMapper',
'MappingManager',
'LineInitialMapper',
# global objects
'CONTROL_TAG',
'PAULI_BASIS',
Expand Down
8 changes: 7 additions & 1 deletion cirq-core/cirq/transformers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@
two_qubit_gate_product_tabulation,
)

from cirq.transformers.routing import AbstractInitialMapper, HardCodedInitialMapper, MappingManager

from cirq.transformers.routing import (
MappingManager,
HardCodedInitialMapper,
AbstractInitialMapper,
LineInitialMapper,
Comment thread
ammareltigani marked this conversation as resolved.
Outdated
Comment thread
ammareltigani marked this conversation as resolved.
Outdated
)

from cirq.transformers.target_gatesets import (
create_transformer_with_kwargs,
Expand Down
1 change: 1 addition & 0 deletions cirq-core/cirq/transformers/routing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@

from cirq.transformers.routing.initial_mapper import AbstractInitialMapper, HardCodedInitialMapper
from cirq.transformers.routing.mapping_manager import MappingManager
from cirq.transformers.routing.line_initial_mapper import LineInitialMapper
237 changes: 237 additions & 0 deletions cirq-core/cirq/transformers/routing/line_initial_mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
# Copyright 2022 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Concrete implementation of AbstractInitialMapper that places lines of qubits onto the device."""
Comment thread
ammareltigani marked this conversation as resolved.
Outdated

from typing import Dict, Optional, TYPE_CHECKING
import networkx as nx

from cirq import value, _compat
from cirq.transformers import routing
Comment thread
ammareltigani marked this conversation as resolved.
Outdated

if TYPE_CHECKING:
import cirq


@value.value_equality
class LineInitialMapper(routing.AbstractInitialMapper):
"""Places logical qubits in the circuit onto physical qubits on the device."""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update docstring to reflect that specific strategy used by the LineInitialMapper.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the docstring in initial_mapping(). Should I move it to the docstring for the class or keep it there?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a brief description to class docstring as well, highlighting high level details like an overview of strategy, expected complexity etc.


def __init__(self, device_graph: nx.Graph) -> None:
"""Initializes a LineInitialMapper.

Args:
device_graph: device graph
"""
# TODO: Tanuj, should this logic be done instead at the beginning of the routing transformer
# so that we don't have to repeat it for each InitialMapper and for the MappingManager?
Comment thread
ammareltigani marked this conversation as resolved.
Outdated
if nx.is_directed(device_graph):
self.device_graph = nx.DiGraph()
self.device_graph.add_nodes_from(sorted(list(device_graph.nodes(data=True))))
self.device_graph.add_edges_from(sorted(list(device_graph.edges)))
else:
self.device_graph = nx.Graph()
self.device_graph.add_nodes_from(sorted(list(device_graph.nodes(data=True))))
self.device_graph.add_edges_from(
sorted(list(sorted(edge) for edge in device_graph.edges))
)
Comment thread
ammareltigani marked this conversation as resolved.
Outdated

def _make_circuit_graph(self, circuit: 'cirq.AbstractCircuit') -> nx.Graph:
Comment thread
ammareltigani marked this conversation as resolved.
Outdated
"""Creates a (potentially incomplete) qubit connectivity graph of the circuit.

Iterates over the moments circuit from left to right drawing edges between logical qubits
Comment thread
ammareltigani marked this conversation as resolved.
Outdated
that:
(1) have degree < 2, and
(2) that are involved in a 2-qubit operation in the current moment.
Comment thread
ammareltigani marked this conversation as resolved.
Outdated
At this point the graph is forest of paths and/or simple cycles. For each simple cycle, make
Comment thread
ammareltigani marked this conversation as resolved.
Outdated
it a path by removing the last edge that was added to it.

Args:
circuit: the input circuit with logical qubits

Returns:
The (potentially incomplete) qubit connectivity graph of the circuit.
Comment thread
ammareltigani marked this conversation as resolved.
Outdated
"""
circuit_graph = nx.Graph()
edge_order = 0
for op in circuit.all_operations():
circuit_graph.add_nodes_from(op.qubits)
Comment thread
ammareltigani marked this conversation as resolved.
Outdated
if len(op.qubits) == 2 and all(
circuit_graph.degree[op.qubits[i]] < 2 for i in range(2)
):
circuit_graph.add_edge(*op.qubits, edge_order=edge_order)
edge_order += 1
found = True
while found:
try:
cycle = nx.find_cycle(circuit_graph)
edge_to_remove = max(
cycle, key=lambda x: circuit_graph.edges[x[0], x[1]]['edge_order']
)
circuit_graph.remove_edge(*edge_to_remove)
except nx.exception.NetworkXNoCycle:
found = False
Comment thread
ammareltigani marked this conversation as resolved.
Outdated
return circuit_graph
Comment thread
ammareltigani marked this conversation as resolved.
Outdated

def initial_mapping(self, circuit: 'cirq.AbstractCircuit') -> Dict['cirq.Qid', 'cirq.Qid']:
"""Maps disjoint lines of logical qubits onto lines of physical qubits.

Starting from the center physical qubit on the device, attempts to map disjoint lines of
logical qubits given by the circuit graph onto one long line of physical qubits on the
device, greedily maximizing each physical qubit's degree.
If this mapping cannot be completed as one long line of qubits in the circuit graph mapped
to qubits in the device graph, the line can be split as several line segments and then we:
(i) Map first line segment.
(ii) Find another high degree vertex in G near the center.
(iii) Map the second line segment
(iv) etc.

Args:
circuit: the input circuit with logical qubits

Returns:
a dictionary that maps logical qubits in the circuit (keys) to physical qubits on the
device (values).
"""
return self._initial_mapping(circuit.freeze())

@_compat.cached_method
Comment thread
ammareltigani marked this conversation as resolved.
Outdated
def _initial_mapping(self, circuit: 'cirq.FrozenCircuit') -> Dict['cirq.Qid', 'cirq.Qid']:
"""Maps disjoint lines of logical qubits onto lines of physical qubits.

Helper for 'initial_mapping' that takes a (hashable) frozen circuit to cache the result.

Args:
circuit: the input circuit with logical qubits

Returns:
a dictionary that maps logical qubits in the circuit (keys) to physical qubits on the
device (values).
"""
qubit_map: Dict['cirq.Qid', 'cirq.Qid'] = {}
circuit_graph = self._make_circuit_graph(circuit)
physical_center = nx.center(self.device_graph)[0]
Comment thread
ammareltigani marked this conversation as resolved.
Outdated

def next_physical(current_physical: 'cirq.Qid') -> 'cirq.Qid':
# use current physical if last logical line ended before mapping to it.
if self.device_graph.nodes[current_physical]["mapped"] is False:
return current_physical
Comment thread
ammareltigani marked this conversation as resolved.
Outdated
# else greedily map to highest degree neighbor that that is available
sorted_neighbors = sorted(
self.device_graph.neighbors(current_physical),
key=lambda x: self.device_graph.degree(x),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be sorting based on "unused degree", i.e. x > y if x has more unmapped neighbors than y?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll keep note of this change and try it during benchmarking!

)
for neighbor in reversed(sorted_neighbors):
if self.device_graph.nodes[neighbor]["mapped"] is False:
return neighbor
# if cannot map onto one long line of physical qubits, then break down into multiple
# small lines by finding nearest available qubit to the physical center
return self._closest_unmapped_qubit(physical_center)

def next_logical(current_logical: 'cirq.Qid') -> Optional['cirq.Qid']:
for neighbor in circuit_graph.neighbors(current_logical):
if circuit_graph.nodes[neighbor]["mapped"] is False:
return neighbor
return None

for pq in self.device_graph.nodes:
self.device_graph.nodes[pq]["mapped"] = False
Comment thread
ammareltigani marked this conversation as resolved.
Outdated
for lq in circuit_graph.nodes:
circuit_graph.nodes[lq]["mapped"] = False
Comment thread
ammareltigani marked this conversation as resolved.
Outdated

current_physical = physical_center
for logical_cc in nx.connected_components(circuit_graph):
Comment thread
ammareltigani marked this conversation as resolved.
Outdated
if len(logical_cc) == 1:
continue
Comment thread
ammareltigani marked this conversation as resolved.
Outdated
# logical_cc is a set, make it a sorted list to guarantee deterministic behavior
logical_cc = sorted(logical_cc)

current_physical = next_physical(current_physical)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's maybe remove this line and also remove the if current_logical is not None: condition in line 169?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line if current_logical is not None: tests for an edge case where if mapping n qubits on an n-qubit device we should not call next_physical when finished mapping the last logical qubit else will raise an error.

# start by mapping a logical line from one of its endpoints.
current_logical = next(q for q in logical_cc if circuit_graph.degree(q) == 1)

while current_logical is not None:
self.device_graph.nodes[current_physical]["mapped"] = True
circuit_graph.nodes[current_logical]["mapped"] = True
qubit_map[current_logical] = current_physical
current_logical = next_logical(current_logical)
if current_logical is not None:
current_physical = next_physical(current_physical)

self._map_remaining_qubits(circuit, circuit_graph, qubit_map)
return qubit_map
Comment thread
tanujkhattar marked this conversation as resolved.

def _map_remaining_qubits(
self,
circuit: 'cirq.AbstractCircuit',
circuit_graph: nx.Graph,
qubit_map: Dict['cirq.Qid', 'cirq.Qid'],
) -> None:
"""Maps remaining qubits that are not incident to edges in the circuit_graph.

First maps logical qubits that interact in circuit but have missing edges in the circuit
graph. Then maps logical qubits that don't interact with any other logical qubits in the
circuit.

Args:
circuit_graph: the (potentially incomplete) qubit connectivity graph of the circuit.
qubit_map: the mapping of logical to physical qubits done so far.
"""
for op in circuit.all_operations():
if len(op.qubits) == 2:
q1, q2 = op.qubits
if q1 not in qubit_map.keys():
physical = self._closest_unmapped_qubit(qubit_map[q2])
qubit_map[q1] = physical
self.device_graph.nodes[physical]["mapped"] = True
# 'elif' because at least one must be mapped already
elif q2 not in qubit_map.keys():
physical = self._closest_unmapped_qubit(qubit_map[q1])
qubit_map[q2] = physical
self.device_graph.nodes[physical]["mapped"] = True

for isolated_qubit in (q for q in circuit_graph.nodes if q not in qubit_map):
physical = self._closest_unmapped_qubit(qubit_map[next(iter(qubit_map))])
qubit_map[isolated_qubit] = physical
self.device_graph.nodes[physical]["mapped"] = True
Comment thread
ammareltigani marked this conversation as resolved.
Outdated

def _closest_unmapped_qubit(self, source: 'cirq.Qid') -> 'cirq.Qid':
"""Finds the closest available neighbor to a physical qubit 'source' on the device.

Args:
source: a physical qubit on the device.

Returns:
the closest available physical qubit to 'source'.

Raises:
ValueError: if there are no available qubits left on the device.
"""
for _, successors in nx.bfs_successors(self.device_graph, source):
for successor in successors:
if self.device_graph.nodes[successor]["mapped"] is False:
return successor
raise ValueError("No available physical qubits left on the device.")

def _value_equality_values_(self):
"""Two LineInitialMappers are equal if they execute on the same device graph."""
return (
tuple(self.device_graph.nodes),
tuple(self.device_graph.edges),
nx.is_directed(self.device_graph),
)

def __repr__(self):
graph_type = type(self.device_graph).__name__
return f'cirq.LineInitialMapper(nx.{graph_type}({dict(self.device_graph.adjacency())}))'
Loading