From 284cbb7e4359e6d9c50f5876486b73c6ca959230 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Mon, 18 Apr 2022 14:35:44 -0700 Subject: [PATCH 1/8] [cirqflow] Hardcoded Qubit Placement --- cirq-core/cirq/__init__.py | 1 + cirq-core/cirq/devices/__init__.py | 1 + cirq-core/cirq/devices/named_topologies.py | 44 + .../cirq/devices/named_topologies_test.py | 20 +- cirq-google/cirq_google/__init__.py | 1 + .../cirq_google/json_resolver_cache.py | 1 + .../cirq.google.HardcodedQubitPlacer.json | 781 ++++++++++++++++++ .../cirq.google.HardcodedQubitPlacer.repr | 1 + .../cirq_google/json_test_data/spec.py | 1 + cirq-google/cirq_google/workflow/__init__.py | 1 + .../cirq_google/workflow/qubit_placement.py | 70 +- .../workflow/qubit_placement_test.py | 40 + docs/google/concepts.ipynb | 2 +- docs/google/qubit-placement.ipynb | 424 ++++++++++ 14 files changed, 1384 insertions(+), 4 deletions(-) create mode 100644 cirq-google/cirq_google/json_test_data/cirq.google.HardcodedQubitPlacer.json create mode 100644 cirq-google/cirq_google/json_test_data/cirq.google.HardcodedQubitPlacer.repr create mode 100644 docs/google/qubit-placement.ipynb diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index 040eee77296..a7a0a78682c 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -102,6 +102,7 @@ LineTopology, TiltedSquareLattice, get_placements, + is_valid_placement, draw_placements, ) diff --git a/cirq-core/cirq/devices/__init__.py b/cirq-core/cirq/devices/__init__.py index d75cd967c19..61bebac9f9a 100644 --- a/cirq-core/cirq/devices/__init__.py +++ b/cirq-core/cirq/devices/__init__.py @@ -32,6 +32,7 @@ LineTopology, TiltedSquareLattice, get_placements, + is_valid_placement, draw_placements, ) diff --git a/cirq-core/cirq/devices/named_topologies.py b/cirq-core/cirq/devices/named_topologies.py index ef7d6842d0e..2f4f98b0359 100644 --- a/cirq-core/cirq/devices/named_topologies.py +++ b/cirq-core/cirq/devices/named_topologies.py @@ -290,6 +290,37 @@ def get_placements( return small_to_bigs +def _is_valid_placement_helper( + big_graph: nx.Graph, small_mapped: nx.Graph, small_to_big_mapping: Dict +): + """Helper function for `is_valid_placement` that assumes the mapping of `small_graph` has + already occurred. + + This is so we don't duplicate work when checking placements during `draw_placements`. + """ + subgraph = big_graph.subgraph(small_to_big_mapping.values()) + return (subgraph.nodes == small_mapped.nodes) and (subgraph.edges == small_mapped.edges) + + +def is_valid_placement(big_graph: nx.Graph, small_graph: nx.Graph, small_to_big_mapping: Dict): + """Return whether the given placement is a valid placement of small_graph onto big_graph. + + This is done by making sure all the nodes and edges on the mapped version of `small_graph` + are present in `big_graph`. + + Args: + big_graph: A larger graph we're placing `small_graph` onto. + small_graph: A smaller, (potential) sub-graph to validate the given mapping. + small_to_big_mapping: A mappings from `small_graph` nodes to `big_graph` + nodes. After the mapping occurs, we check whether all of the mapped nodes and + edges exist on `big_graph`. + """ + small_mapped = nx.relabel_nodes(small_graph, small_to_big_mapping) + return _is_valid_placement_helper( + big_graph=big_graph, small_mapped=small_mapped, small_to_big_mapping=small_to_big_mapping + ) + + def draw_placements( big_graph: nx.Graph, small_graph: nx.Graph, @@ -297,6 +328,7 @@ def draw_placements( max_plots: int = 20, axes: Sequence[plt.Axes] = None, tilted=True, + bad_placement_callback=None, ): """Draw a visualization of placements from small_graph onto big_graph using Matplotlib. @@ -312,6 +344,9 @@ def draw_placements( `max_plots` plots. axes: Optional list of matplotlib Axes to contain the drawings. tilted: Whether to draw gridlike graphs in the ordinary cartesian or tilted plane. + bad_placement_callback: If provided, we check that the given mappings are valid. If not, + this callback is called. The callback should accept `ax` and `i` keyword arguments + for the current axis and mapping index, respectively. """ if len(small_to_big_mappings) > max_plots: # coverage: ignore @@ -331,6 +366,15 @@ def draw_placements( ax = plt.gca() small_mapped = nx.relabel_nodes(small_graph, small_to_big_map) + if bad_placement_callback is not None: + # coverage: ignore + if not _is_valid_placement_helper( + big_graph=big_graph, + small_mapped=small_mapped, + small_to_big_mapping=small_to_big_map, + ): + bad_placement_callback(ax=ax, i=i) + draw_gridlike(big_graph, ax=ax, tilted=tilted) draw_gridlike( small_mapped, diff --git a/cirq-core/cirq/devices/named_topologies_test.py b/cirq-core/cirq/devices/named_topologies_test.py index 476af99a87b..de6e370f5e5 100644 --- a/cirq-core/cirq/devices/named_topologies_test.py +++ b/cirq-core/cirq/devices/named_topologies_test.py @@ -17,7 +17,14 @@ import cirq import networkx as nx import pytest -from cirq import draw_gridlike, LineTopology, TiltedSquareLattice, get_placements, draw_placements +from cirq import ( + draw_gridlike, + LineTopology, + TiltedSquareLattice, + get_placements, + draw_placements, + is_valid_placement, +) @pytest.mark.parametrize('width, height', list(itertools.product([1, 2, 3, 24], repeat=2))) @@ -119,3 +126,14 @@ def test_get_placements(): draw_placements(syc23, topo.graph, placements[::3], axes=axes) for ax in axes: ax.scatter.assert_called() + + +def test_is_valid_placement(): + topo = TiltedSquareLattice(4, 2) + syc23 = TiltedSquareLattice(8, 4).graph + placements = get_placements(syc23, topo.graph) + for placement in placements: + assert is_valid_placement(syc23, topo.graph, placement) + + bad_placement = topo.nodes_to_gridqubits(offset=(100, 100)) + assert not is_valid_placement(syc23, topo.graph, bad_placement) diff --git a/cirq-google/cirq_google/__init__.py b/cirq-google/cirq_google/__init__.py index 04c0f23119f..63b23caf149 100644 --- a/cirq-google/cirq_google/__init__.py +++ b/cirq-google/cirq_google/__init__.py @@ -140,6 +140,7 @@ CouldNotPlaceError, NaiveQubitPlacer, RandomDevicePlacer, + HardcodedQubitPlacer, ProcessorRecord, EngineProcessorRecord, SimulatedProcessorRecord, diff --git a/cirq-google/cirq_google/json_resolver_cache.py b/cirq-google/cirq_google/json_resolver_cache.py index db11e11eae0..e95d3f45d83 100644 --- a/cirq-google/cirq_google/json_resolver_cache.py +++ b/cirq-google/cirq_google/json_resolver_cache.py @@ -61,5 +61,6 @@ def _class_resolver_dictionary() -> Dict[str, ObjectFactory]: 'cirq.google.SimulatedProcessorRecord': cirq_google.SimulatedProcessorRecord, # pylint: disable=line-too-long 'cirq.google.SimulatedProcessorWithLocalDeviceRecord': cirq_google.SimulatedProcessorWithLocalDeviceRecord, + 'cirq.google.HardcodedQubitPlacer': cirq_google.HardcodedQubitPlacer, # pylint: enable=line-too-long } diff --git a/cirq-google/cirq_google/json_test_data/cirq.google.HardcodedQubitPlacer.json b/cirq-google/cirq_google/json_test_data/cirq.google.HardcodedQubitPlacer.json new file mode 100644 index 00000000000..4f6aa7b51aa --- /dev/null +++ b/cirq-google/cirq_google/json_test_data/cirq.google.HardcodedQubitPlacer.json @@ -0,0 +1,781 @@ +{ + "cirq_type": "cirq.google.HardcodedQubitPlacer", + "mapping": [ + [ + { + "cirq_type": "TiltedSquareLattice", + "width": 2, + "height": 2 + }, + [ + [ + [ + 0, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 4, + "col": 2 + } + ], + [ + [ + 1, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 2 + } + ], + [ + [ + 2, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 2 + } + ], + [ + [ + 1, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 3 + } + ], + [ + [ + 1, + -1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 1 + } + ] + ] + ], + [ + { + "cirq_type": "TiltedSquareLattice", + "width": 2, + "height": 3 + }, + [ + [ + [ + 2, + -1 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 1 + } + ], + [ + [ + 1, + -1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 1 + } + ], + [ + [ + 2, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 2 + } + ], + [ + [ + 0, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 4, + "col": 2 + } + ], + [ + [ + 1, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 2 + } + ], + [ + [ + 1, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 3 + } + ] + ] + ], + [ + { + "cirq_type": "TiltedSquareLattice", + "width": 3, + "height": 2 + }, + [ + [ + [ + 2, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 3 + } + ], + [ + [ + 1, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 3 + } + ], + [ + [ + 2, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 2 + } + ], + [ + [ + 0, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 4, + "col": 2 + } + ], + [ + [ + 1, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 2 + } + ], + [ + [ + 1, + -1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 1 + } + ] + ] + ], + [ + { + "cirq_type": "TiltedSquareLattice", + "width": 3, + "height": 3 + }, + [ + [ + [ + 2, + -1 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 1 + } + ], + [ + [ + 1, + -1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 1 + } + ], + [ + [ + 2, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 2 + } + ], + [ + [ + 2, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 3 + } + ], + [ + [ + 1, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 3 + } + ], + [ + [ + 0, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 4, + "col": 2 + } + ], + [ + [ + 1, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 2 + } + ], + [ + [ + 3, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 7, + "col": 2 + } + ] + ] + ], + [ + { + "cirq_type": "TiltedSquareLattice", + "width": 4, + "height": 2 + }, + [ + [ + [ + 1, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 2 + } + ], + [ + [ + 2, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 2 + } + ], + [ + [ + 0, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 4, + "col": 2 + } + ], + [ + [ + 1, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 3 + } + ], + [ + [ + 1, + -1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 1 + } + ], + [ + [ + 2, + 2 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 4 + } + ], + [ + [ + 2, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 3 + } + ], + [ + [ + 3, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 7, + "col": 3 + } + ] + ] + ], + [ + { + "cirq_type": "TiltedSquareLattice", + "width": 4, + "height": 3 + }, + [ + [ + [ + 2, + -1 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 1 + } + ], + [ + [ + 1, + -1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 1 + } + ], + [ + [ + 2, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 2 + } + ], + [ + [ + 2, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 3 + } + ], + [ + [ + 3, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 7, + "col": 3 + } + ], + [ + [ + 1, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 3 + } + ], + [ + [ + 2, + 2 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 4 + } + ], + [ + [ + 0, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 4, + "col": 2 + } + ], + [ + [ + 1, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 2 + } + ], + [ + [ + 3, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 7, + "col": 2 + } + ] + ] + ], + [ + { + "cirq_type": "TiltedSquareLattice", + "width": 5, + "height": 2 + }, + [ + [ + [ + 2, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 3 + } + ], + [ + [ + 3, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 7, + "col": 3 + } + ], + [ + [ + 1, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 3 + } + ], + [ + [ + 2, + 2 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 4 + } + ], + [ + [ + 2, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 2 + } + ], + [ + [ + 0, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 4, + "col": 2 + } + ], + [ + [ + 1, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 2 + } + ], + [ + [ + 3, + 2 + ], + { + "cirq_type": "GridQubit", + "row": 7, + "col": 4 + } + ], + [ + [ + 1, + -1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 1 + } + ] + ] + ], + [ + { + "cirq_type": "TiltedSquareLattice", + "width": 5, + "height": 3 + }, + [ + [ + [ + 2, + -1 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 1 + } + ], + [ + [ + 1, + -1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 1 + } + ], + [ + [ + 2, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 2 + } + ], + [ + [ + 2, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 3 + } + ], + [ + [ + 3, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 7, + "col": 3 + } + ], + [ + [ + 1, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 3 + } + ], + [ + [ + 2, + 2 + ], + { + "cirq_type": "GridQubit", + "row": 6, + "col": 4 + } + ], + [ + [ + 0, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 4, + "col": 2 + } + ], + [ + [ + 1, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 5, + "col": 2 + } + ], + [ + [ + 4, + 1 + ], + { + "cirq_type": "GridQubit", + "row": 8, + "col": 3 + } + ], + [ + [ + 3, + 2 + ], + { + "cirq_type": "GridQubit", + "row": 7, + "col": 4 + } + ], + [ + [ + 3, + 0 + ], + { + "cirq_type": "GridQubit", + "row": 7, + "col": 2 + } + ] + ] + ] + ] +} \ No newline at end of file diff --git a/cirq-google/cirq_google/json_test_data/cirq.google.HardcodedQubitPlacer.repr b/cirq-google/cirq_google/json_test_data/cirq.google.HardcodedQubitPlacer.repr new file mode 100644 index 00000000000..249eaf134fe --- /dev/null +++ b/cirq-google/cirq_google/json_test_data/cirq.google.HardcodedQubitPlacer.repr @@ -0,0 +1 @@ +cirq_google.HardcodedQubitPlacer(mapping={cirq.TiltedSquareLattice(width=2, height=2): {(0, 0): cirq.GridQubit(4, 2),(1, 0): cirq.GridQubit(5, 2),(2, 0): cirq.GridQubit(6, 2),(1, 1): cirq.GridQubit(5, 3),(1, -1): cirq.GridQubit(5, 1)},cirq.TiltedSquareLattice(width=2, height=3): {(2, -1): cirq.GridQubit(6, 1),(1, -1): cirq.GridQubit(5, 1),(2, 0): cirq.GridQubit(6, 2),(0, 0): cirq.GridQubit(4, 2),(1, 0): cirq.GridQubit(5, 2),(1, 1): cirq.GridQubit(5, 3)},cirq.TiltedSquareLattice(width=3, height=2): {(2, 1): cirq.GridQubit(6, 3),(1, 1): cirq.GridQubit(5, 3),(2, 0): cirq.GridQubit(6, 2),(0, 0): cirq.GridQubit(4, 2),(1, 0): cirq.GridQubit(5, 2),(1, -1): cirq.GridQubit(5, 1)},cirq.TiltedSquareLattice(width=3, height=3): {(2, -1): cirq.GridQubit(6, 1),(1, -1): cirq.GridQubit(5, 1),(2, 0): cirq.GridQubit(6, 2),(2, 1): cirq.GridQubit(6, 3),(1, 1): cirq.GridQubit(5, 3),(0, 0): cirq.GridQubit(4, 2),(1, 0): cirq.GridQubit(5, 2),(3, 0): cirq.GridQubit(7, 2)},cirq.TiltedSquareLattice(width=4, height=2): {(1, 0): cirq.GridQubit(5, 2),(2, 0): cirq.GridQubit(6, 2),(0, 0): cirq.GridQubit(4, 2),(1, 1): cirq.GridQubit(5, 3),(1, -1): cirq.GridQubit(5, 1),(2, 2): cirq.GridQubit(6, 4),(2, 1): cirq.GridQubit(6, 3),(3, 1): cirq.GridQubit(7, 3)},cirq.TiltedSquareLattice(width=4, height=3): {(2, -1): cirq.GridQubit(6, 1),(1, -1): cirq.GridQubit(5, 1),(2, 0): cirq.GridQubit(6, 2),(2, 1): cirq.GridQubit(6, 3),(3, 1): cirq.GridQubit(7, 3),(1, 1): cirq.GridQubit(5, 3),(2, 2): cirq.GridQubit(6, 4),(0, 0): cirq.GridQubit(4, 2),(1, 0): cirq.GridQubit(5, 2),(3, 0): cirq.GridQubit(7, 2)},cirq.TiltedSquareLattice(width=5, height=2): {(2, 1): cirq.GridQubit(6, 3),(3, 1): cirq.GridQubit(7, 3),(1, 1): cirq.GridQubit(5, 3),(2, 2): cirq.GridQubit(6, 4),(2, 0): cirq.GridQubit(6, 2),(0, 0): cirq.GridQubit(4, 2),(1, 0): cirq.GridQubit(5, 2),(3, 2): cirq.GridQubit(7, 4),(1, -1): cirq.GridQubit(5, 1)},cirq.TiltedSquareLattice(width=5, height=3): {(2, -1): cirq.GridQubit(6, 1),(1, -1): cirq.GridQubit(5, 1),(2, 0): cirq.GridQubit(6, 2),(2, 1): cirq.GridQubit(6, 3),(3, 1): cirq.GridQubit(7, 3),(1, 1): cirq.GridQubit(5, 3),(2, 2): cirq.GridQubit(6, 4),(0, 0): cirq.GridQubit(4, 2),(1, 0): cirq.GridQubit(5, 2),(4, 1): cirq.GridQubit(8, 3),(3, 2): cirq.GridQubit(7, 4),(3, 0): cirq.GridQubit(7, 2)}}) diff --git a/cirq-google/cirq_google/json_test_data/spec.py b/cirq-google/cirq_google/json_test_data/spec.py index 8fa96036aaf..2ac6d6b7075 100644 --- a/cirq-google/cirq_google/json_test_data/spec.py +++ b/cirq-google/cirq_google/json_test_data/spec.py @@ -71,6 +71,7 @@ 'ExecutableGroupResultFilesystemRecord', 'NaiveQubitPlacer', 'RandomDevicePlacer', + 'HardcodedQubitPlacer', 'EngineProcessorRecord', 'SimulatedProcessorRecord', 'SimulatedProcessorWithLocalDeviceRecord', diff --git a/cirq-google/cirq_google/workflow/__init__.py b/cirq-google/cirq_google/workflow/__init__.py index 661f09b2ce5..baa0fd7ac2e 100644 --- a/cirq-google/cirq_google/workflow/__init__.py +++ b/cirq-google/cirq_google/workflow/__init__.py @@ -23,6 +23,7 @@ CouldNotPlaceError, NaiveQubitPlacer, RandomDevicePlacer, + HardcodedQubitPlacer, ) from cirq_google.workflow.processor_record import ( diff --git a/cirq-google/cirq_google/workflow/qubit_placement.py b/cirq-google/cirq_google/workflow/qubit_placement.py index cb1e1921831..e45b7762037 100644 --- a/cirq-google/cirq_google/workflow/qubit_placement.py +++ b/cirq-google/cirq_google/workflow/qubit_placement.py @@ -17,13 +17,14 @@ import abc import dataclasses from functools import lru_cache -from typing import Dict, Any, Tuple, List, Callable, TYPE_CHECKING +from typing import Dict, Any, Tuple, List, Callable, TYPE_CHECKING, Hashable import numpy as np import cirq from cirq import _compat -from cirq.devices.named_topologies import get_placements +from cirq.devices.named_topologies import get_placements, NamedTopology +from cirq.protocols import obj_to_dict_helper from cirq_google.workflow._device_shim import _Device_dot_get_nx_graph if TYPE_CHECKING: @@ -107,6 +108,71 @@ def default_topo_node_to_qubit(node: Any) -> cirq.Qid: return cirq.LineQubit(node) +class HardcodedQubitPlacer(QubitPlacer): + def __init__( + self, + mapping: Dict[cirq.NamedTopology, Dict[Any, cirq.Qid]], + topo_node_to_qubit_func: Callable[[Hashable], cirq.Qid] = default_topo_node_to_qubit, + ): + self.mapping = mapping + self.topo_node_to_qubit_func = topo_node_to_qubit_func + + def place_circuit( + self, + circuit: cirq.AbstractCircuit, + problem_topology: NamedTopology, + shared_rt_info: 'cg.SharedRuntimeInfo', + rs: np.random.RandomState, + ) -> Tuple[cirq.FrozenCircuit, Dict[Any, cirq.Qid]]: + try: + nt_mapping = self.mapping[problem_topology] + except KeyError as e: + raise CouldNotPlaceError(str(e)) + + circuit_mapping = { + self.topo_node_to_qubit_func(nt_node): gridq for nt_node, gridq in nt_mapping.items() + } + + circuit = circuit.unfreeze().transform_qubits(circuit_mapping).freeze() + return circuit, circuit_mapping + + def __repr__(self) -> str: + return f'cirq_google.HardcodedQubitPlacer(mapping={_compat.proper_repr(self.mapping)})' + + @classmethod + def _json_namespace_(cls) -> str: + return 'cirq.google' + + def _json_dict_(self): + d = obj_to_dict_helper(self, attribute_names=[]) + + # Nested dict: turn both levels to list(key_value_pair) + mapping = {topo: list(placement.items()) for topo, placement in self.mapping.items()} + mapping = list(mapping.items()) + d['mapping'] = mapping + return d + + @classmethod + def _from_json_dict_(cls, **kwargs): + # From nested list(key_value_pair) to dictionary + mapping: Dict[cirq.NamedTopology, Dict[Any, 'cirq.Qid']] = {} + for topo, placement_kvs in kwargs['mapping']: + placement: Dict[Hashable, 'cirq.Qid'] = {} + for k, v in placement_kvs: + if isinstance(k, list): + k = tuple(k) + placement[k] = v + mapping[topo] = placement + + return cls(mapping=mapping) + + def __eq__(self, other): + if not isinstance(other, HardcodedQubitPlacer): + return False + + return self.mapping == other.mapping + + @lru_cache() def _cached_get_placements( problem_topo: 'cirq.NamedTopology', device: 'cirq.Device' diff --git a/cirq-google/cirq_google/workflow/qubit_placement_test.py b/cirq-google/cirq_google/workflow/qubit_placement_test.py index 72ac78afe48..0c0a4fe8a6f 100644 --- a/cirq-google/cirq_google/workflow/qubit_placement_test.py +++ b/cirq-google/cirq_google/workflow/qubit_placement_test.py @@ -11,6 +11,8 @@ # 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. +import itertools + import pytest import cirq @@ -145,3 +147,41 @@ class BadDevice(cirq.Device): shared_rt_info=cg.SharedRuntimeInfo(run_id='1', device=BadDevice()), rs=np.random.RandomState(1), ) + + +def _all_offset_placements(device_graph, offset=(4, 2), min_sidelength=2, max_sidelength=5): + # Generate candidate tilted square lattice topologies + sidelens = list(itertools.product(range(min_sidelength, max_sidelength + 1), repeat=2)) + topos = [cirq.TiltedSquareLattice(width, height) for width, height in sidelens] + + # Make placements using TiltedSquareLattice.nodes_to_gridqubits offset parameter + placements = {topo: topo.nodes_to_gridqubits(offset=offset) for topo in topos} + + # Only allow placements that are valid on the device graph + placements = { + topo: mapping + for topo, mapping in placements.items() + if cirq.is_valid_placement(device_graph, topo.graph, mapping) + } + return placements + + +def test_hardcoded_qubit_placer(): + + rainbow_record = cg.SimulatedProcessorWithLocalDeviceRecord('rainbow') + rainbow_device = rainbow_record.get_device() + rainbow_graph = rainbow_device.metadata.nx_graph + hardcoded = cg.HardcodedQubitPlacer(_all_offset_placements(rainbow_graph)) + + topo = cirq.TiltedSquareLattice(3, 2) + circuit = cirq.experiments.random_rotations_between_grid_interaction_layers_circuit( + qubits=sorted(topo.nodes_as_gridqubits()), depth=4 + ) + shared_rt_info = cg.SharedRuntimeInfo(run_id='example', device=rainbow_device) + + rs = np.random.RandomState(10) + placed_c, placement = hardcoded.place_circuit( + circuit, problem_topology=topo, shared_rt_info=shared_rt_info, rs=rs + ) + cirq.is_valid_placement(rainbow_graph, topo.graph, placement) + assert isinstance(placed_c, cirq.FrozenCircuit) diff --git a/docs/google/concepts.ipynb b/docs/google/concepts.ipynb index 5829196369d..cd450129178 100644 --- a/docs/google/concepts.ipynb +++ b/docs/google/concepts.ipynb @@ -296,4 +296,4 @@ }, "nbformat": 4, "nbformat_minor": 0 -} +} \ No newline at end of file diff --git a/docs/google/qubit-placement.ipynb b/docs/google/qubit-placement.ipynb new file mode 100644 index 00000000000..6487adfd9f5 --- /dev/null +++ b/docs/google/qubit-placement.ipynb @@ -0,0 +1,424 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e0c57b3a", + "metadata": { + "id": "cedf868076a2" + }, + "source": [ + "##### Copyright 2020 The Cirq Developers" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "020331ec", + "metadata": { + "cellView": "form", + "id": "906e07f6e562" + }, + "outputs": [], + "source": [ + "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# https://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License." + ] + }, + { + "cell_type": "markdown", + "id": "gross-longer", + "metadata": { + "id": "e0e0473de3dc" + }, + "source": [ + "# Qubit Placement\n", + "\n", + "This notebooks walks through qubit placement runtime features exposed through the `cirq_google.workflow` tools." + ] + }, + { + "cell_type": "markdown", + "id": "3609cf6b", + "metadata": { + "id": "OQdp_uLYZz8l" + }, + "source": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " View on QuantumAI\n", + " \n", + " Run in Google Colab\n", + " \n", + " View source on GitHub\n", + " \n", + " Download notebook\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0f3974f5", + "metadata": { + "id": "bd9529db1c0b" + }, + "outputs": [], + "source": [ + "try:\n", + " import cirq\n", + "except ImportError:\n", + " print(\"installing cirq...\")\n", + " !pip install --quiet cirq\n", + " print(\"installed cirq.\")\n", + " import cirq" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "subjective-sponsorship", + "metadata": { + "id": "d16b510265bc" + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "from matplotlib import pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "id": "sporting-inspector", + "metadata": { + "id": "55baa01dd094" + }, + "source": [ + "## Target Device\n", + "\n", + "First, we get an example target device and---crucially---its qubit connectivity graph." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "acknowledged-declaration", + "metadata": { + "id": "270d450e9959" + }, + "outputs": [], + "source": [ + "from cirq_google.workflow import SimulatedProcessorWithLocalDeviceRecord\n", + "rainbow_record = SimulatedProcessorWithLocalDeviceRecord('rainbow')\n", + "rainbow_device = rainbow_record.get_device()\n", + "rainbow_graph = rainbow_device.metadata.nx_graph\n", + "\n", + "_ = cirq.draw_gridlike(rainbow_graph, tilted=False)" + ] + }, + { + "cell_type": "markdown", + "id": "roman-scanning", + "metadata": { + "id": "02d2ad587499" + }, + "source": [ + "## Target problem topology\n", + "\n", + "We'll use a `NamedTopology` to talk about the graph connectivity of our circuit. In this case, we'll construct a random circuit on a `cirq.TiltedSquareLattice` topology of a given width and height." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "weighted-transaction", + "metadata": { + "id": "378b7f5863c6" + }, + "outputs": [], + "source": [ + "topo = cirq.TiltedSquareLattice(3, 2)\n", + "_ = cirq.draw_gridlike(topo.graph, tilted=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "northern-houston", + "metadata": { + "id": "11baafdac30b" + }, + "outputs": [], + "source": [ + "circuit = cirq.experiments.random_rotations_between_grid_interaction_layers_circuit(\n", + " qubits=sorted(topo.nodes_as_gridqubits()),\n", + " depth=4,\n", + ")\n", + "\n", + "from cirq.contrib.svg import SVGCircuit\n", + "SVGCircuit(circuit)" + ] + }, + { + "cell_type": "markdown", + "id": "automated-entertainment", + "metadata": { + "id": "5d5c0610f7b7" + }, + "source": [ + "### Verify circuit connectivity\n", + "\n", + "We use a topology to generate a random circuit. Now we can extract the implied circuit topology from the two qubit gates within to verify that it is indeed the topology we requested:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "acting-disease", + "metadata": { + "id": "239a76ff14a1" + }, + "outputs": [], + "source": [ + "from cirq.contrib.routing import get_circuit_connectivity\n", + "circuit_graph = get_circuit_connectivity(circuit)\n", + "_ = cirq.draw_gridlike(circuit_graph, tilted=False)" + ] + }, + { + "cell_type": "markdown", + "id": "banned-scotland", + "metadata": { + "id": "2ce2f7b6fd0c" + }, + "source": [ + "## QubitPlacer\n", + "\n", + "The following classes follow the `QubitPlacer` interface. In particular, there is a method `place_circuit` which maps arbitary input qubits in a circuit to qubits that exist on the device. It accepts a named problem topology and other runtime information to provide more context to the qubit placers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "objective-webcam", + "metadata": { + "id": "b5e6d19aa069" + }, + "outputs": [], + "source": [ + "# set up some other required arguments.\n", + "# In a real `cirq_google.workflow.execute()` run, these will be\n", + "# handled for you.\n", + "\n", + "from cirq_google.workflow import SharedRuntimeInfo\n", + "shared_rt_info = SharedRuntimeInfo(run_id='example', device=rainbow_device)\n", + "\n", + "rs = np.random.RandomState(10)" + ] + }, + { + "cell_type": "markdown", + "id": "optical-express", + "metadata": { + "id": "cc192438688a" + }, + "source": [ + "## RandomDevicePlacer\n", + "\n", + "The `RandomDevicePlacer` will find random, valid placements. On a technical level, this uses networkx subgraph monomorphism routines to map the problem topology to the device graph." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "introductory-oxford", + "metadata": { + "id": "71e57db1ecbb" + }, + "outputs": [], + "source": [ + "from cirq_google.workflow import NaiveQubitPlacer, RandomDevicePlacer, HardcodedQubitPlacer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "outdoor-greeting", + "metadata": { + "id": "c25208b28e51" + }, + "outputs": [], + "source": [ + "rdp = RandomDevicePlacer()\n", + "\n", + "placed_c, placement = rdp.place_circuit(circuit, problem_topology=topo, shared_rt_info=shared_rt_info, rs=rs)\n", + "cirq.draw_placements(rainbow_graph, circuit_graph, [placement])" + ] + }, + { + "cell_type": "markdown", + "id": "drawn-apache", + "metadata": { + "id": "606fb6683da7" + }, + "source": [ + "## NaiveQubitPlacer\n", + "\n", + "As a fallback, you can rely on `NaiveQubitPlacer` which will map input qubits to output qubits. Be careful though! This means you have to choose your qubits as part of circuit construction, which is not advised if you're using `cirq_google.workflow` best practices." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "prepared-crisis", + "metadata": { + "id": "b574f442aaea" + }, + "outputs": [], + "source": [ + "naive = NaiveQubitPlacer()\n", + "\n", + "placed_c, placement = naive.place_circuit(circuit, problem_topology=topo, shared_rt_info=shared_rt_info, rs=rs)\n", + "cirq.draw_placements(rainbow_graph, circuit_graph, [placement])" + ] + }, + { + "cell_type": "markdown", + "id": "american-shelter", + "metadata": { + "id": "6addabf2d6d3" + }, + "source": [ + "## HardcodedQubitPlacer\n", + "\n", + "If you want ultimate control over qubit placement but still want to decouple your `cg.QuantumExecutable`s from a particular device or configuration, you can use the `HardcodedQubitPlacer` to place your circuits at runtime but from a pre-specified list of valid placements.\n", + "\n", + "Here, we introduce a helper function to generate placements for all `TiltedSquareLattice` topologies anchored from qubit `(4, 2)`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cardiovascular-review", + "metadata": { + "id": "4a96d68cd342" + }, + "outputs": [], + "source": [ + "import itertools\n", + "\n", + "def all_offset_placements(device_graph, offset=(4, 2), min_sidelength=2, max_sidelength=5):\n", + " # Generate candidate tilted square lattice topologies\n", + " sidelens = list(itertools.product(range(min_sidelength, max_sidelength + 1), repeat=2))\n", + " topos = [cirq.TiltedSquareLattice(width, height) for width, height in sidelens]\n", + " \n", + " # Make placements using TiltedSquareLattice.nodes_to_gridqubits offset parameter\n", + " placements = {topo: topo.nodes_to_gridqubits(offset=offset) for topo in topos}\n", + " \n", + " # Only allow placements that are valid on the device graph\n", + " placements = {topo: mapping for topo, mapping in placements.items()\n", + " if cirq.is_valid_placement(device_graph, topo.graph, mapping)}\n", + " return placements\n" + ] + }, + { + "cell_type": "markdown", + "id": "decimal-mississippi", + "metadata": { + "id": "cac066fdef56" + }, + "source": [ + "The constructor for `HardcodedQubitPlacer` takes in a mapping from named topology to a \"placement\". Each placement is a mapping from named topology node to device qubit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "engaged-harbor", + "metadata": { + "id": "28464222f0c8" + }, + "outputs": [], + "source": [ + "hardcoded = HardcodedQubitPlacer(all_offset_placements(rainbow_graph))\n", + "\n", + "placed_c, placement = hardcoded.place_circuit(circuit, problem_topology=topo, shared_rt_info=shared_rt_info, rs=rs)\n", + "cirq.draw_placements(rainbow_graph, circuit_graph, [placement])" + ] + }, + { + "cell_type": "markdown", + "id": "foster-python", + "metadata": { + "id": "7fdcf57337b0" + }, + "source": [ + "#### All hardcoded placements\n", + "\n", + "For completeness, the following figure shows all hardcoded placements. If you request one of the supported `TiltedSquareLattice` topology, you'll get the depicted mapping. If you request a topology not in the hardcoded list, you will receive an error. The `RandomDevicePlacer` (in contrast) will always succeed if the topology can be placed on the device." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bizarre-extraction", + "metadata": { + "id": "ccbd12b1f13e" + }, + "outputs": [], + "source": [ + "from math import ceil\n", + "\n", + "n_col = 3\n", + "n_row = int(ceil(len(hardcoded.mapping)/n_col))\n", + "fig, axes = plt.subplots(n_row, n_col, figsize=(4*n_col, 3*n_row))\n", + "axes = axes.reshape(-1)\n", + "for i, (topo, mapping) in enumerate(hardcoded.mapping.items()):\n", + "\n", + " axes[i].set_title(f'{topo.width}x{topo.height}')\n", + " cirq.draw_placements(rainbow_graph, topo.graph, [mapping], \n", + " tilted=False, axes=axes[i:i+1])\n", + " \n", + "fig.suptitle(\"All hardcoded placements\", fontsize=14)\n", + "fig.tight_layout()" + ] + } + ], + "metadata": { + "colab": { + "name": "qubit-placement.ipynb", + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.9" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} \ No newline at end of file From 4eadf5015daa45b81fa2f34246960958f31aa829 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Mon, 18 Apr 2022 14:47:06 -0700 Subject: [PATCH 2/8] coverage --- .../cirq_google/workflow/qubit_placement.py | 1 + .../workflow/qubit_placement_test.py | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/cirq-google/cirq_google/workflow/qubit_placement.py b/cirq-google/cirq_google/workflow/qubit_placement.py index e45b7762037..cd9f995e743 100644 --- a/cirq-google/cirq_google/workflow/qubit_placement.py +++ b/cirq-google/cirq_google/workflow/qubit_placement.py @@ -168,6 +168,7 @@ def _from_json_dict_(cls, **kwargs): def __eq__(self, other): if not isinstance(other, HardcodedQubitPlacer): + # coverage: ignore return False return self.mapping == other.mapping diff --git a/cirq-google/cirq_google/workflow/qubit_placement_test.py b/cirq-google/cirq_google/workflow/qubit_placement_test.py index 0c0a4fe8a6f..8525ac07e10 100644 --- a/cirq-google/cirq_google/workflow/qubit_placement_test.py +++ b/cirq-google/cirq_google/workflow/qubit_placement_test.py @@ -185,3 +185,26 @@ def test_hardcoded_qubit_placer(): ) cirq.is_valid_placement(rainbow_graph, topo.graph, placement) assert isinstance(placed_c, cirq.FrozenCircuit) + + +def test_hqp_missing_placement(): + hqp = cg.HardcodedQubitPlacer({cirq.LineTopology(5): dict(enumerate(cirq.LineQubit.range(5)))}) + + circuit = cirq.testing.random_circuit(cirq.LineQubit.range(5), n_moments=2, op_density=1) + shared_rt_info = cg.SharedRuntimeInfo(run_id='example') + rs = np.random.RandomState(10) + placed_c, _ = hqp.place_circuit( + circuit, problem_topology=cirq.LineTopology(5), shared_rt_info=shared_rt_info, rs=rs + ) + assert isinstance(placed_c, cirq.AbstractCircuit) + + circuit = cirq.testing.random_circuit(cirq.LineQubit.range(6), n_moments=2, op_density=1) + with pytest.raises(cg.CouldNotPlaceError): + hqp.place_circuit( + circuit, problem_topology=cirq.LineTopology(6), shared_rt_info=shared_rt_info, rs=rs + ) + + +def test_hqp_repr(): + hqp = cg.HardcodedQubitPlacer({cirq.LineTopology(5): dict(enumerate(cirq.LineQubit.range(5)))}) + cirq.testing.assert_equivalent_repr(hqp, global_vals={'cirq_google': cg}) From ab3423750df5927b1e8e1084db4afad6fc4cefa5 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Mon, 18 Apr 2022 14:54:11 -0700 Subject: [PATCH 3/8] autosave strikes again --- docs/google/qubit-placement.ipynb | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/docs/google/qubit-placement.ipynb b/docs/google/qubit-placement.ipynb index 6487adfd9f5..205ee62c1c8 100644 --- a/docs/google/qubit-placement.ipynb +++ b/docs/google/qubit-placement.ipynb @@ -403,22 +403,9 @@ }, "kernelspec": { "display_name": "Python 3", - "language": "python", "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.9" } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 0 } \ No newline at end of file From 19c743854faee2c016e01311428bfd1f26327d36 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Mon, 18 Apr 2022 14:55:22 -0700 Subject: [PATCH 4/8] what is happening --- docs/google/concepts.ipynb | 2 +- docs/google/qubit-placement.ipynb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/google/concepts.ipynb b/docs/google/concepts.ipynb index cd450129178..5829196369d 100644 --- a/docs/google/concepts.ipynb +++ b/docs/google/concepts.ipynb @@ -296,4 +296,4 @@ }, "nbformat": 4, "nbformat_minor": 0 -} \ No newline at end of file +} diff --git a/docs/google/qubit-placement.ipynb b/docs/google/qubit-placement.ipynb index 205ee62c1c8..07a5c1c4a57 100644 --- a/docs/google/qubit-placement.ipynb +++ b/docs/google/qubit-placement.ipynb @@ -408,4 +408,4 @@ }, "nbformat": 4, "nbformat_minor": 0 -} \ No newline at end of file +} From 767cb06ee049e75a494f54e7d79a2ccfade5bb28 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Wed, 20 Apr 2022 15:05:22 -0700 Subject: [PATCH 5/8] Update cirq-core/cirq/devices/named_topologies.py Co-authored-by: Tanuj Khattar --- cirq-core/cirq/devices/named_topologies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/devices/named_topologies.py b/cirq-core/cirq/devices/named_topologies.py index 2f4f98b0359..8f0c31ef9d7 100644 --- a/cirq-core/cirq/devices/named_topologies.py +++ b/cirq-core/cirq/devices/named_topologies.py @@ -311,7 +311,7 @@ def is_valid_placement(big_graph: nx.Graph, small_graph: nx.Graph, small_to_big_ Args: big_graph: A larger graph we're placing `small_graph` onto. small_graph: A smaller, (potential) sub-graph to validate the given mapping. - small_to_big_mapping: A mappings from `small_graph` nodes to `big_graph` + small_to_big_mapping: A mapping from `small_graph` nodes to `big_graph` nodes. After the mapping occurs, we check whether all of the mapped nodes and edges exist on `big_graph`. """ From a25ef29154ba3e8abbe42f631c579b41ddcbb742 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Wed, 20 Apr 2022 15:13:30 -0700 Subject: [PATCH 6/8] some of the review comments --- cirq-core/cirq/devices/named_topologies.py | 19 +++++++++--- .../cirq_google/workflow/qubit_placement.py | 30 +++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/cirq-core/cirq/devices/named_topologies.py b/cirq-core/cirq/devices/named_topologies.py index 8f0c31ef9d7..43321767186 100644 --- a/cirq-core/cirq/devices/named_topologies.py +++ b/cirq-core/cirq/devices/named_topologies.py @@ -15,7 +15,18 @@ import abc import warnings from dataclasses import dataclass -from typing import Dict, List, Tuple, Any, Sequence, Union, Iterable, TYPE_CHECKING +from typing import ( + Dict, + List, + Tuple, + Any, + Sequence, + Union, + Iterable, + TYPE_CHECKING, + Callable, + Optional, +) import networkx as nx from matplotlib import pyplot as plt @@ -327,8 +338,8 @@ def draw_placements( small_to_big_mappings: Sequence[Dict], max_plots: int = 20, axes: Sequence[plt.Axes] = None, - tilted=True, - bad_placement_callback=None, + tilted: bool = True, + bad_placement_callback: Optional[Callable[[plt.Axes, int], None]] = None, ): """Draw a visualization of placements from small_graph onto big_graph using Matplotlib. @@ -373,7 +384,7 @@ def draw_placements( small_mapped=small_mapped, small_to_big_mapping=small_to_big_map, ): - bad_placement_callback(ax=ax, i=i) + bad_placement_callback(ax, i) draw_gridlike(big_graph, ax=ax, tilted=tilted) draw_gridlike( diff --git a/cirq-google/cirq_google/workflow/qubit_placement.py b/cirq-google/cirq_google/workflow/qubit_placement.py index cd9f995e743..c042dd8ef47 100644 --- a/cirq-google/cirq_google/workflow/qubit_placement.py +++ b/cirq-google/cirq_google/workflow/qubit_placement.py @@ -114,6 +114,23 @@ def __init__( mapping: Dict[cirq.NamedTopology, Dict[Any, cirq.Qid]], topo_node_to_qubit_func: Callable[[Hashable], cirq.Qid] = default_topo_node_to_qubit, ): + """A placement strategy that uses the explicitly provided `mapping`. + + Args: + mapping: The hardcoded placements. This provides a placement for each supported + `cirq.NamedTopology`. The topology serves as the key for the mapping dictionary. + Each placement is a dictionary mapping topology node to final `cirq.Qid` device + qubit. + topo_node_to_qubit_func: A function that maps from `cirq.NamedTopology` nodes + to `cirq.Qid`. There is a correspondence between nodes and the "abstract" Qids + used to construct the un-placed circuit. We use this function to interpret + the provided mappings. By default: nodes which are tuples correspond + to `cirq.GridQubit`s; otherwise `cirq.LineQubit`. + + Note: + The attribute `topo_node_to_qubit_func` is not preserved in JSON serialization. This + bit of plumbing does not affect the placement behavior. + """ self.mapping = mapping self.topo_node_to_qubit_func = topo_node_to_qubit_func @@ -124,6 +141,19 @@ def place_circuit( shared_rt_info: 'cg.SharedRuntimeInfo', rs: np.random.RandomState, ) -> Tuple[cirq.FrozenCircuit, Dict[Any, cirq.Qid]]: + """Place a circuit according to the hardcoded placements. + + Args: + circuit: The circuit. + problem_topology: The topologies (i.e. connectivity) of the circuit, use to look + up the placement in `self.mapping`. + shared_rt_info: A `cg.SharedRuntimeInfo` object; ignored for hardcoded placement. + rs: A `RandomState`; ignored for hardcoded placement. + + Returns: + A tuple of a new frozen circuit with the qubits placed and a mapping from input + qubits or nodes to output qubits. + """ try: nt_mapping = self.mapping[problem_topology] except KeyError as e: From 2511293c63b3419ffe544fd2a9f478ed01bc405c Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Wed, 20 Apr 2022 15:20:01 -0700 Subject: [PATCH 7/8] other review comments --- cirq-google/cirq_google/workflow/qubit_placement.py | 10 +++++----- .../cirq_google/workflow/qubit_placement_test.py | 9 ++++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cirq-google/cirq_google/workflow/qubit_placement.py b/cirq-google/cirq_google/workflow/qubit_placement.py index c042dd8ef47..0a2dd156040 100644 --- a/cirq-google/cirq_google/workflow/qubit_placement.py +++ b/cirq-google/cirq_google/workflow/qubit_placement.py @@ -131,7 +131,7 @@ def __init__( The attribute `topo_node_to_qubit_func` is not preserved in JSON serialization. This bit of plumbing does not affect the placement behavior. """ - self.mapping = mapping + self._mapping = mapping self.topo_node_to_qubit_func = topo_node_to_qubit_func def place_circuit( @@ -155,7 +155,7 @@ def place_circuit( qubits or nodes to output qubits. """ try: - nt_mapping = self.mapping[problem_topology] + nt_mapping = self._mapping[problem_topology] except KeyError as e: raise CouldNotPlaceError(str(e)) @@ -167,7 +167,7 @@ def place_circuit( return circuit, circuit_mapping def __repr__(self) -> str: - return f'cirq_google.HardcodedQubitPlacer(mapping={_compat.proper_repr(self.mapping)})' + return f'cirq_google.HardcodedQubitPlacer(mapping={_compat.proper_repr(self._mapping)})' @classmethod def _json_namespace_(cls) -> str: @@ -177,7 +177,7 @@ def _json_dict_(self): d = obj_to_dict_helper(self, attribute_names=[]) # Nested dict: turn both levels to list(key_value_pair) - mapping = {topo: list(placement.items()) for topo, placement in self.mapping.items()} + mapping = {topo: list(placement.items()) for topo, placement in self._mapping.items()} mapping = list(mapping.items()) d['mapping'] = mapping return d @@ -201,7 +201,7 @@ def __eq__(self, other): # coverage: ignore return False - return self.mapping == other.mapping + return self._mapping == other._mapping @lru_cache() diff --git a/cirq-google/cirq_google/workflow/qubit_placement_test.py b/cirq-google/cirq_google/workflow/qubit_placement_test.py index 8525ac07e10..41e47b8b725 100644 --- a/cirq-google/cirq_google/workflow/qubit_placement_test.py +++ b/cirq-google/cirq_google/workflow/qubit_placement_test.py @@ -205,6 +205,13 @@ def test_hqp_missing_placement(): ) -def test_hqp_repr(): +def test_hqp_equality(): hqp = cg.HardcodedQubitPlacer({cirq.LineTopology(5): dict(enumerate(cirq.LineQubit.range(5)))}) + hqp2 = cg.HardcodedQubitPlacer({cirq.LineTopology(5): dict(enumerate(cirq.LineQubit.range(5)))}) + assert hqp == hqp2 cirq.testing.assert_equivalent_repr(hqp, global_vals={'cirq_google': cg}) + + hqp3 = cg.HardcodedQubitPlacer( + {cirq.LineTopology(5): dict(enumerate(cirq.LineQubit.range(1, 5 + 1)))} + ) + assert hqp != hqp3 From 85872bd76317a3580955c2ddef88f984f8e362ed Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Thu, 21 Apr 2022 17:12:29 -0700 Subject: [PATCH 8/8] document exception --- cirq-google/cirq_google/workflow/qubit_placement.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cirq-google/cirq_google/workflow/qubit_placement.py b/cirq-google/cirq_google/workflow/qubit_placement.py index 0a2dd156040..819d7afaefa 100644 --- a/cirq-google/cirq_google/workflow/qubit_placement.py +++ b/cirq-google/cirq_google/workflow/qubit_placement.py @@ -153,6 +153,10 @@ def place_circuit( Returns: A tuple of a new frozen circuit with the qubits placed and a mapping from input qubits or nodes to output qubits. + + Raises: + CouldNotPlaceError: if the given problem_topology is not present in the hardcoded + mapping. """ try: nt_mapping = self._mapping[problem_topology]