diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index 2a88a9f7c2c..9f29a232a80 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -91,6 +91,12 @@ NoiseModel, SymmetricalQidPair, UNCONSTRAINED_DEVICE, + NamedTopology, + draw_gridlike, + LineTopology, + TiltedSquareLattice, + get_placements, + draw_placements, ) from cirq.experiments import ( diff --git a/cirq-core/cirq/devices/__init__.py b/cirq-core/cirq/devices/__init__.py index 8fce1350752..1060f7aea0e 100644 --- a/cirq-core/cirq/devices/__init__.py +++ b/cirq-core/cirq/devices/__init__.py @@ -38,3 +38,12 @@ NoiseModel, ConstantQubitNoiseModel, ) + +from cirq.devices.named_topologies import ( + NamedTopology, + draw_gridlike, + LineTopology, + TiltedSquareLattice, + get_placements, + draw_placements, +) diff --git a/cirq-core/cirq/devices/named_topologies.py b/cirq-core/cirq/devices/named_topologies.py new file mode 100644 index 00000000000..f0454c53793 --- /dev/null +++ b/cirq-core/cirq/devices/named_topologies.py @@ -0,0 +1,316 @@ +# Copyright 2021 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. + +import abc +import dataclasses +import warnings +from dataclasses import dataclass +from typing import Dict, List, Tuple, Any, Sequence, Union, Iterable, TYPE_CHECKING + +import networkx as nx +from cirq.devices import GridQubit +from cirq.protocols.json_serialization import obj_to_dict_helper +from matplotlib import pyplot as plt + +if TYPE_CHECKING: + import cirq + + +def dataclass_json_dict(obj: Any, namespace: str = None) -> Dict[str, Any]: + return obj_to_dict_helper(obj, [f.name for f in dataclasses.fields(obj)], namespace=namespace) + + +class NamedTopology(metaclass=abc.ABCMeta): + """A topology (graph) with a name. + + "Named topologies" provide a mapping from a simple dataclass to a unique graph for categories + of relevant topologies. Relevant topologies may be hardware dependant, but common topologies + are linear (1D) and rectangular grid topologies. + """ + + name: str = NotImplemented + """A name that uniquely identifies this topology.""" + + n_nodes: int = NotImplemented + """The number of nodes in the topology.""" + + graph: nx.Graph = NotImplemented + """A networkx graph representation of the topology.""" + + +_GRIDLIKE_NODE = Union['cirq.GridQubit', Tuple[int, int]] + + +def _node_and_coordinates( + nodes: Iterable[_GRIDLIKE_NODE], +) -> Iterable[Tuple[_GRIDLIKE_NODE, Tuple[int, int]]]: + """Yield tuples whose first element is the input node and the second is guaranteed to be a tuple + of two integers. The input node can be a tuple of ints or a GridQubit.""" + for node in nodes: + if isinstance(node, GridQubit): + yield node, (node.row, node.col) + else: + x, y = node + yield node, (x, y) + + +def draw_gridlike( + graph: nx.Graph, ax: plt.Axes = None, tilted: bool = True, **kwargs +) -> Dict[Any, Tuple[int, int]]: + """Draw a grid-like graph using Matplotlib. + + This wraps nx.draw_networkx to produce a matplotlib drawing of the graph. Nodes + should be two-dimensional gridlike objects. + + Args: + graph: A NetworkX graph whose nodes are (row, column) coordinates or cirq.GridQubits. + ax: Optional matplotlib axis to use for drawing. + tilted: If True, directly position as (row, column); otherwise, + rotate 45 degrees to accommodate google-style diagonal grids. + kwargs: Additional arguments to pass to `nx.draw_networkx`. + + Returns: + A positions dictionary mapping nodes to (x, y) coordinates suitable for future calls + to NetworkX plotting functionality. + """ + if ax is None: + ax = plt.gca() # coverage: ignore + + if tilted: + pos = {node: (y, -x) for node, (x, y) in _node_and_coordinates(graph.nodes)} + else: + pos = {node: (x + y, y - x) for node, (x, y) in _node_and_coordinates(graph.nodes)} + + nx.draw_networkx(graph, pos=pos, ax=ax, **kwargs) + ax.axis('equal') + return pos + + +@dataclass(frozen=True) +class LineTopology(NamedTopology): + """A 1D linear topology. + + Node indices are contiguous integers starting from 0 with edges between + adjacent integers. + + Args: + n_nodes: The number of nodes in a line. + """ + + n_nodes: int + + def __post_init__(self): + if self.n_nodes <= 1: + raise ValueError("`n_nodes` must be greater than 1.") + object.__setattr__(self, 'name', f'line-{self.n_nodes}') + graph = nx.from_edgelist( + [(i1, i2) for i1, i2 in zip(range(self.n_nodes), range(1, self.n_nodes))] + ) + object.__setattr__(self, 'graph', graph) + + def draw(self, ax=None, tilted: bool = True, **kwargs) -> Dict[Any, Tuple[int, int]]: + """Draw this graph using Matplotlib. + + Args: + ax: Optional matplotlib axis to use for drawing. + tilted: If True, draw as a horizontal line. Otherwise, draw on a diagonal. + kwargs: Additional arguments to pass to `nx.draw_networkx`. + """ + g2 = nx.relabel_nodes(self.graph, {n: (n, 1) for n in self.graph.nodes}) + return draw_gridlike(g2, ax=ax, tilted=tilted, **kwargs) + + def _json_dict_(self) -> Dict[str, Any]: + return dataclass_json_dict(self) + + +@dataclass(frozen=True) +class TiltedSquareLattice(NamedTopology): + """A grid lattice rotated 45-degrees. + + This topology is based on Google devices where plaquettes consist of four qubits in a square + connected to a central qubit: + + x x + x + x x + + The corner nodes are not connected to each other. `width` and `height` refer to the rectangle + formed by rotating the lattice 45 degrees. `width` and `height` are measured in half-unit + cells, or equivalently half the number of central nodes. + An example diagram of this topology is shown below. It is a + "tilted-square-lattice-6-4" with width 6 and height 4. + + x + │ + x────X────x + │ │ │ + x────X────x────X────x + │ │ │ │ + x────X────x────X───x + │ │ │ + x────X────x + │ + x + + Nodes are 2-tuples of integers which may be negative. Please see `get_placements` for + mapping this topology to a GridQubit Device. + """ + + width: int + height: int + + def __post_init__(self): + if self.width <= 0: + raise ValueError("Width must be a positive integer") + if self.height <= 0: + raise ValueError("Height must be a positive integer") + + object.__setattr__(self, 'name', f'tilted-square-lattice-{self.width}-{self.height}') + + rect1 = set( + (i + j, i - j) for i in range(self.width // 2 + 1) for j in range(self.height // 2 + 1) + ) + rect2 = set( + ((i + j) // 2, (i - j) // 2) + for i in range(1, self.width + 1, 2) + for j in range(1, self.height + 1, 2) + ) + nodes = rect1 | rect2 + g = nx.Graph() + for node in nodes: + for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]: + neighbor = (node[0] + dx, node[1] + dy) + if neighbor in nodes: + g.add_edge(node, neighbor) + + object.__setattr__(self, 'graph', g) + + # The number of edges = width * height (see unit tests). This can be seen if you remove + # all vertices and replace edges with dots. + # The formula for the number of vertices is not that nice, but you can derive it by + # summing big and small Xes in the asciiart in the docstring. + # There are (width//2 + 1) * (height//2 + 1) small xes and + # ((width + 1)//2) * ((height + 1)//2) big ones. + n_nodes = (self.width // 2 + 1) * (self.height // 2 + 1) + n_nodes += ((self.width + 1) // 2) * ((self.height + 1) // 2) + object.__setattr__(self, 'n_nodes', n_nodes) + + def draw(self, ax=None, tilted=True, **kwargs): + """Draw this graph using Matplotlib. + + Args: + ax: Optional matplotlib axis to use for drawing. + tilted: If True, directly position as (row, column); otherwise, + rotate 45 degrees to accommodate the diagonal nature of this topology. + kwargs: Additional arguments to pass to `nx.draw_networkx`. + """ + return draw_gridlike(self.graph, ax=ax, tilted=tilted, **kwargs) + + def nodes_as_gridqubits(self) -> List['cirq.GridQubit']: + """Get the graph nodes as cirq.GridQubit""" + return [GridQubit(r, c) for r, c in sorted(self.graph.nodes)] + + def _json_dict_(self) -> Dict[str, Any]: + return dataclass_json_dict(self) + + +def get_placements( + big_graph: nx.Graph, small_graph: nx.Graph, max_placements=100_000 +) -> List[Dict]: + """Get 'placements' mapping small_graph nodes onto those of `big_graph`. + + This function considers monomorphisms with a restriction: we restrict only to unique set + of `big_graph` qubits. Some monomorphisms may be basically + the same mapping just rotated/flipped which we purposefully exclude. This could + exclude meaningful differences like using the same qubits but having the edges assigned + differently, but it prevents the number of placements from blowing up. + + Args: + big_graph: The parent, super-graph. We often consider the case where this is a + nx.Graph representation of a Device whose nodes are `cirq.Qid`s like `GridQubit`s. + small_graph: The subgraph. We often consider the case where this is a NamedTopology + graph. + max_placements: Raise a value error if there are more than this many placement + possibilities. It is possible to use `big_graph`, `small_graph` combinations + that result in an intractable number of placements. + + Raises: + ValueError: if the number of placements exceeds `max_placements`. + + Returns: + A list of placement dictionaries. Each dictionary maps the nodes in `small_graph` to + nodes in `big_graph` with a monomorphic relationship. That's to say: if an edge exists + in `small_graph` between two nodes, it will exist in `big_graph` between the mapped nodes. + """ + matcher = nx.algorithms.isomorphism.GraphMatcher(big_graph, small_graph) + + # de-duplicate rotations, see docstring. + dedupe = {} + for big_to_small_map in matcher.subgraph_monomorphisms_iter(): + dedupe[frozenset(big_to_small_map.keys())] = big_to_small_map + if len(dedupe) > max_placements: + # coverage: ignore + raise ValueError( + f"We found more than {max_placements} placements. Please use a " + f"more constraining `big_graph` or a more constrained `small_graph`." + ) + + small_to_bigs = [] + for big in sorted(dedupe.keys()): + big_to_small_map = dedupe[big] + small_to_big_map = {v: k for k, v in big_to_small_map.items()} + small_to_bigs.append(small_to_big_map) + return small_to_bigs + + +def draw_placements( + big_graph: nx.Graph, + small_graph: nx.Graph, + small_to_big_mappings, + max_plots=20, + axes: Sequence[plt.Axes] = None, +): + """Draw a visualization of placements from small_graph onto big_graph using Matplotlib. + + The entire `big_graph` will be drawn with default blue colored nodes. `small_graph` nodes + and edges will be highlighted with a red color. + """ + if len(small_to_big_mappings) > max_plots: + # coverage: ignore + warnings.warn(f"You've provided a lot of mappings. Only plotting the first {max_plots}") + small_to_big_mappings = small_to_big_mappings[:max_plots] + + call_show = False + if axes is None: + # coverage: ignore + call_show = True + + for i, small_to_big_map in enumerate(small_to_big_mappings): + if axes is not None: + ax = axes[i] + else: + # coverage: ignore + ax = plt.gca() + + small_mapped = nx.relabel_nodes(small_graph, small_to_big_map) + draw_gridlike(big_graph, ax=ax) + draw_gridlike( + small_mapped, node_color='red', edge_color='red', width=2, with_labels=False, ax=ax + ) + ax.axis('equal') + if call_show: + # coverage: ignore + # poor man's multi-axis figure: call plt.show() after each plot + # and jupyter will put the plots one after another. + plt.show() diff --git a/cirq-core/cirq/devices/named_topologies_test.py b/cirq-core/cirq/devices/named_topologies_test.py new file mode 100644 index 00000000000..2379e677aba --- /dev/null +++ b/cirq-core/cirq/devices/named_topologies_test.py @@ -0,0 +1,113 @@ +# Copyright 2021 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. +import itertools +from unittest.mock import MagicMock + +import cirq +import networkx as nx +import pytest +from cirq import ( + draw_gridlike, + LineTopology, + TiltedSquareLattice, + get_placements, + draw_placements, +) + + +@pytest.mark.parametrize('width, height', list(itertools.product([1, 2, 3, 24], repeat=2))) +def test_tilted_square_lattice(width, height): + topo = TiltedSquareLattice(width, height) + assert topo.graph.number_of_edges() == width * height + assert all(1 <= topo.graph.degree[node] <= 4 for node in topo.graph.nodes) + assert topo.name == f'tilted-square-lattice-{width}-{height}' + assert topo.n_nodes == topo.graph.number_of_nodes() + assert nx.is_connected(topo.graph) + assert nx.algorithms.planarity.check_planarity(topo.graph) + + +def test_bad_tilted_square_lattice(): + with pytest.raises(ValueError): + _ = TiltedSquareLattice(0, 3) + with pytest.raises(ValueError): + _ = TiltedSquareLattice(3, 0) + + +def test_tilted_square_methods(): + topo = TiltedSquareLattice(5, 5) + ax = MagicMock() + topo.draw(ax=ax) + ax.scatter.assert_called() + + qubits = topo.nodes_as_gridqubits() + assert all(isinstance(q, cirq.GridQubit) for q in qubits) + + +def test_tilted_square_lattice_n_nodes(): + for width, height in itertools.product(list(range(1, 4 + 1)), repeat=2): + topo = TiltedSquareLattice(width, height) + assert topo.n_nodes == topo.graph.number_of_nodes() + + +def test_line_topology(): + n = 10 + topo = LineTopology(n) + assert topo.n_nodes == n + assert topo.n_nodes == topo.graph.number_of_nodes() + assert all(1 <= topo.graph.degree[node] <= 2 for node in topo.graph.nodes) + assert topo.name == 'line-10' + + ax = MagicMock() + topo.draw(ax=ax) + ax.scatter.assert_called() + + with pytest.raises(ValueError, match='greater than 1.*'): + _ = LineTopology(1) + assert LineTopology(2).n_nodes == 2 + assert LineTopology(2).graph.number_of_nodes() == 2 + + +@pytest.mark.parametrize('tilted', [True, False]) +def test_draw_gridlike(tilted): + graph = nx.grid_2d_graph(3, 3) + ax = MagicMock() + pos = draw_gridlike(graph, tilted=tilted, ax=ax) + ax.scatter.assert_called() + for (row, column), _ in pos.items(): + assert 0 <= row < 3 + assert 0 <= column < 3 + + +@pytest.mark.parametrize('tilted', [True, False]) +def test_draw_gridlike_qubits(tilted): + graph = nx.grid_2d_graph(3, 3) + graph = nx.relabel_nodes(graph, {(r, c): cirq.GridQubit(r, c) for r, c in sorted(graph.nodes)}) + ax = MagicMock() + pos = draw_gridlike(graph, tilted=tilted, ax=ax) + ax.scatter.assert_called() + for q, _ in pos.items(): + assert 0 <= q.row < 3 + assert 0 <= q.col < 3 + + +def test_get_placements(): + topo = TiltedSquareLattice(4, 2) + syc23 = TiltedSquareLattice(8, 4).graph + placements = get_placements(syc23, topo.graph) + assert len(placements) == 12 + + axes = [MagicMock() for _ in range(4)] + draw_placements(syc23, topo.graph, placements[::3], axes=axes) + for ax in axes: + ax.scatter.assert_called() diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index 6a103e71257..76c9a91284b 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -94,6 +94,7 @@ def two_qubit_matrix_gate(matrix): 'LinearDict': cirq.LinearDict, 'LineQubit': cirq.LineQubit, 'LineQid': cirq.LineQid, + 'LineTopology': cirq.LineTopology, 'MatrixGate': cirq.MatrixGate, 'MixedUnitaryChannel': cirq.MixedUnitaryChannel, 'MeasurementKey': cirq.MeasurementKey, @@ -134,6 +135,7 @@ def two_qubit_matrix_gate(matrix): 'SwapPowGate': cirq.SwapPowGate, 'SymmetricalQidPair': cirq.SymmetricalQidPair, 'TaggedOperation': cirq.TaggedOperation, + 'TiltedSquareLattice': cirq.TiltedSquareLattice, 'TrialResult': cirq.Result, # keep support for Cirq < 0.11. 'Result': cirq.Result, 'Rx': cirq.Rx, diff --git a/cirq-core/cirq/protocols/json_test_data/LineTopology.json b/cirq-core/cirq/protocols/json_test_data/LineTopology.json new file mode 100644 index 00000000000..8208b25f2fa --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/LineTopology.json @@ -0,0 +1,4 @@ +{ + "cirq_type": "LineTopology", + "n_nodes": 10 +} \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/LineTopology.repr b/cirq-core/cirq/protocols/json_test_data/LineTopology.repr new file mode 100644 index 00000000000..c5237d09ec3 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/LineTopology.repr @@ -0,0 +1 @@ +cirq.LineTopology(n_nodes=10) diff --git a/cirq-core/cirq/protocols/json_test_data/TiltedSquareLattice.json b/cirq-core/cirq/protocols/json_test_data/TiltedSquareLattice.json new file mode 100644 index 00000000000..dc330fa73df --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/TiltedSquareLattice.json @@ -0,0 +1,5 @@ +{ + "cirq_type": "TiltedSquareLattice", + "width": 2, + "height": 3 +} \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/TiltedSquareLattice.repr b/cirq-core/cirq/protocols/json_test_data/TiltedSquareLattice.repr new file mode 100644 index 00000000000..0b996325b75 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/TiltedSquareLattice.repr @@ -0,0 +1 @@ +cirq.TiltedSquareLattice(width=2, height=3) diff --git a/cirq-core/cirq/protocols/json_test_data/spec.py b/cirq-core/cirq/protocols/json_test_data/spec.py index 440b5b54a73..9684b1ab6ed 100644 --- a/cirq-core/cirq/protocols/json_test_data/spec.py +++ b/cirq-core/cirq/protocols/json_test_data/spec.py @@ -126,6 +126,7 @@ 'SimulatesAmplitudes', 'SimulatesExpectationValues', 'SimulatesFinalState', + 'NamedTopology', # protocols: 'SupportsActOn', 'SupportsActOnQubits', diff --git a/dev_tools/notebooks/isolated_notebook_test.py b/dev_tools/notebooks/isolated_notebook_test.py index 33fcde6e92d..1fecc986adc 100644 --- a/dev_tools/notebooks/isolated_notebook_test.py +++ b/dev_tools/notebooks/isolated_notebook_test.py @@ -39,7 +39,11 @@ # note that these notebooks are still tested in dev_tools/notebook_test.py # Please, always indicate in comments the feature used for easier bookkeeping. -NOTEBOOKS_DEPENDING_ON_UNRELEASED_FEATURES: List[str] = ['docs/noise.ipynb'] +NOTEBOOKS_DEPENDING_ON_UNRELEASED_FEATURES: List[str] = [ + 'docs/noise.ipynb', + # Named topologies + 'docs/named_topologies.ipynb', +] # By default all notebooks should be tested, however, this list contains exceptions to the rule # please always add a reason for skipping. diff --git a/docs/named_topologies.ipynb b/docs/named_topologies.ipynb new file mode 100644 index 00000000000..c1d72159496 --- /dev/null +++ b/docs/named_topologies.ipynb @@ -0,0 +1,285 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "bE6S_bHcuZIg" + }, + "outputs": [], + "source": [ + "##### Copyright 2021 The Cirq Developers" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "form", + "id": "fwpnM5i0uaRZ" + }, + "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", + "metadata": { + "id": "AF-CFuvzPx4x" + }, + "source": [ + "# Named Topologies" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "b692fcab6e07" + }, + "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": "markdown", + "metadata": { + "id": "pigDhoDrO7vr" + }, + "source": [ + "## Setup\n", + "\n", + "Note: this notebook relies on unreleased Cirq features. If you want to try these features, make sure you install cirq via `pip install cirq --pre`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "AXHWCnLzufVf" + }, + "outputs": [], + "source": [ + "try:\n", + " import cirq\n", + "except ImportError:\n", + " print(\"installing cirq...\")\n", + " !pip install --quiet cirq --pre\n", + " print(\"installed cirq.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6zlksjAlum6f" + }, + "outputs": [], + "source": [ + "from typing import Iterable, List, Optional, Sequence\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import networkx as nx" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "09358b36ef8d" + }, + "source": [ + "## TiltedSquareLattice\n", + "\n", + "This is a grid lattice rotated 45-degrees.\n", + "\n", + "This topology is based on Google devices where plaquettes consist of four qubits in a square\n", + "connected to a central qubit:\n", + "\n", + " x x\n", + " x\n", + " x x" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6172a6b67648" + }, + "outputs": [], + "source": [ + "import itertools\n", + "from cirq import TiltedSquareLattice\n", + "\n", + "side_lens = np.arange(1, 4+1)\n", + "l = len(side_lens)\n", + "\n", + "fig, axes = plt.subplots(l, l, figsize=(3.5*l, 3*l))\n", + "for widthi, heighti in itertools.product(np.arange(l), repeat=2):\n", + " width = side_lens[widthi]\n", + " height = side_lens[heighti]\n", + " ax = axes[heighti, widthi]\n", + " topo = TiltedSquareLattice(width, height)\n", + " topo.draw(ax=ax, tilted=False)\n", + " \n", + " if widthi == 0:\n", + " ax.set_ylabel(f'Height {height}', fontsize=14)\n", + " if heighti == l-1:\n", + " ax.set_xlabel(f'Width {width}', fontsize=14)\n", + " \n", + " ax.set_title(f'n = {topo.n_nodes}', fontsize=14)\n", + " \n", + "fig.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "798f5800f37d" + }, + "source": [ + "The corner nodes are not connected to each other. `width` and `height` refer to the rectangle\n", + "formed by rotating the lattice 45 degrees. `width` and `height` are measured in half-unit\n", + "cells, or equivalently half the number of central nodes.\n", + "\n", + "Nodes are 2-tuples of integers which may be negative. Please see `get_placements` for\n", + "mapping this topology to a GridQubit Device." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ad5780182be8" + }, + "source": [ + "## Placement" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6c977db39c1f" + }, + "outputs": [], + "source": [ + "import networkx as nx\n", + "SYC23_GRAPH = nx.from_edgelist([\n", + " ((3, 2), (4, 2)), ((4, 1), (5, 1)), ((4, 2), (4, 1)), \n", + " ((4, 2), (4, 3)), ((4, 2), (5, 2)), ((4, 3), (5, 3)), \n", + " ((5, 1), (5, 0)), ((5, 1), (5, 2)), ((5, 1), (6, 1)), \n", + " ((5, 2), (5, 3)), ((5, 2), (6, 2)), ((5, 3), (5, 4)), \n", + " ((5, 3), (6, 3)), ((5, 4), (6, 4)), ((6, 1), (6, 2)), \n", + " ((6, 2), (6, 3)), ((6, 2), (7, 2)), ((6, 3), (6, 4)), \n", + " ((6, 3), (7, 3)), ((6, 4), (6, 5)), ((6, 4), (7, 4)), \n", + " ((6, 5), (7, 5)), ((7, 2), (7, 3)), ((7, 3), (7, 4)), \n", + " ((7, 3), (8, 3)), ((7, 4), (7, 5)), ((7, 4), (8, 4)), \n", + " ((7, 5), (7, 6)), ((7, 5), (8, 5)), ((8, 3), (8, 4)), \n", + " ((8, 4), (8, 5)), ((8, 4), (9, 4)), \n", + "])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "de2d879d0111" + }, + "outputs": [], + "source": [ + "from cirq import get_placements, draw_placements\n", + "topo = TiltedSquareLattice(4, 2)\n", + "placements = get_placements(SYC23_GRAPH, topo.graph)\n", + "draw_placements(SYC23_GRAPH, topo.graph, placements[::3])\n", + "print('...\\n')\n", + "print(f'{len(placements)} total placements')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "44efeaf1c70e" + }, + "source": [ + "## LineTopology\n", + "\n", + "This is a 1D linear topology.\n", + "\n", + "Node indices are contiguous integers starting from 0 with edges between\n", + "adjacent integers.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "90d5b377b10c" + }, + "outputs": [], + "source": [ + "from cirq import LineTopology\n", + "\n", + "lens = np.arange(3, 12+1, 3)\n", + "l = len(lens)\n", + "fig, axes = plt.subplots(1,l, figsize=(3.5*l, 3*1))\n", + "\n", + "for ax, n_nodes in zip(axes, lens):\n", + " LineTopology(n_nodes).draw(ax=ax, tilted=False)\n", + " ax.set_title(f'n = {n_nodes}')\n", + " \n", + "fig.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "47b899ba53dc" + }, + "outputs": [], + "source": [ + "topo = LineTopology(9)\n", + "placements = get_placements(SYC23_GRAPH, topo.graph)\n", + "draw_placements(SYC23_GRAPH, topo.graph, placements[::300])\n", + "print('...\\n')\n", + "print(f'{len(placements)} total placements')" + ] + } + ], + "metadata": { + "colab": { + "name": "named_topologies.ipynb", + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +}