Skip to content

Commit f95de7f

Browse files
committed
[runtime] QubitPlacer part 1
1 parent e311059 commit f95de7f

19 files changed

+229
-17
lines changed

check/pytest-changed-files-and-incremental-coverage

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ fi
8686
source dev_tools/pypath
8787

8888
# Run tests while producing coverage files.
89-
check/pytest "${changed_python_tests[@]}" \
90-
"${cov_changed_python_file_dirs[@]}" \
89+
check/pytest ${changed_python_tests[@]} \
90+
${cov_changed_python_file_dirs[@]} \
9191
--cov-report=annotate \
9292
--cov-config=dev_tools/conf/.coveragerc
9393
pytest_result=$?

cirq-core/cirq/devices/named_topologies.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
from typing import Dict, List, Tuple, Any, Sequence, Union, Iterable, TYPE_CHECKING
2020

2121
import networkx as nx
22+
from matplotlib import pyplot as plt
23+
24+
from cirq import _compat
2225
from cirq.devices import GridQubit, LineQubit
2326
from cirq.protocols.json_serialization import obj_to_dict_helper
24-
from matplotlib import pyplot as plt
2527

2628
if TYPE_CHECKING:
2729
import cirq
@@ -137,6 +139,9 @@ def draw(self, ax=None, tilted: bool = True, **kwargs) -> Dict[Any, Tuple[int, i
137139
def _json_dict_(self) -> Dict[str, Any]:
138140
return dataclass_json_dict(self)
139141

142+
def __repr__(self) -> str:
143+
return _compat.dataclass_repr(self)
144+
140145

141146
@dataclass(frozen=True)
142147
class TiltedSquareLattice(NamedTopology):
@@ -237,6 +242,9 @@ def nodes_to_gridqubits(self, offset=(0, 0)) -> Dict[Tuple[int, int], 'cirq.Grid
237242
def _json_dict_(self) -> Dict[str, Any]:
238243
return dataclass_json_dict(self)
239244

245+
def __repr__(self) -> str:
246+
return _compat.dataclass_repr(self)
247+
240248

241249
def get_placements(
242250
big_graph: nx.Graph, small_graph: nx.Graph, max_placements=100_000

cirq-core/cirq/devices/named_topologies_test.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ def test_tilted_square_lattice(width, height):
3636
assert nx.is_connected(topo.graph)
3737
assert nx.algorithms.planarity.check_planarity(topo.graph)
3838

39+
cirq.testing.assert_equivalent_repr(topo)
40+
3941

4042
def test_bad_tilted_square_lattice():
4143
with pytest.raises(ValueError):
@@ -82,6 +84,8 @@ def test_line_topology():
8284
assert LineTopology(2).n_nodes == 2
8385
assert LineTopology(2).graph.number_of_nodes() == 2
8486

87+
cirq.testing.assert_equivalent_repr(topo)
88+
8589

8690
def test_line_topology_nodes_as_qubits():
8791
for n in range(2, 10, 2):

cirq-google/cirq_google/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@
136136
ExecutableGroupResultFilesystemRecord,
137137
QuantumRuntimeConfiguration,
138138
execute,
139+
QubitPlacer,
140+
NaiveQubitPlacer,
139141
)
140142

141143
from cirq_google import experimental

cirq-google/cirq_google/json_resolver_cache.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,5 @@ def _class_resolver_dictionary() -> Dict[str, ObjectFactory]:
5454
'cirq.google.ExecutableGroupResultFilesystemRecord': cirq_google.ExecutableGroupResultFilesystemRecord,
5555
# pylint: enable=line-too-long
5656
'cirq.google.QuantumRuntimeConfiguration': cirq_google.QuantumRuntimeConfiguration,
57+
'cirq.google.NaiveQubitPlacer': cirq_google.NaiveQubitPlacer,
5758
}

cirq-google/cirq_google/json_test_data/cirq.google.ExecutableResult.json

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,31 @@
1212
},
1313
"runtime_info": {
1414
"cirq_type": "cirq.google.RuntimeInfo",
15-
"execution_index": 5
15+
"execution_index": 5,
16+
"qubit_placement": [
17+
[
18+
[
19+
0,
20+
0
21+
],
22+
{
23+
"cirq_type": "GridQubit",
24+
"row": 5,
25+
"col": 5
26+
}
27+
],
28+
[
29+
[
30+
1,
31+
1
32+
],
33+
{
34+
"cirq_type": "GridQubit",
35+
"row": 6,
36+
"col": 6
37+
}
38+
]
39+
]
1640
},
1741
"raw_data": {
1842
"cirq_type": "Result",

cirq-google/cirq_google/json_test_data/cirq.google.ExecutableResult.repr

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"cirq_type": "cirq.google.NaiveQubitPlacer"
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cirq_google.NaiveQubitPlacer()
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
22
"cirq_type": "cirq.google.RuntimeInfo",
3-
"execution_index": 5
3+
"execution_index": 5,
4+
"qubit_placement": null
45
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
cirq_google.RuntimeInfo(execution_index=5)
1+
cirq_google.RuntimeInfo(execution_index=5, qubit_placement=None)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
cirq_google.SharedRuntimeInfo(run_id='my run')
1+
cirq_google.SharedRuntimeInfo(run_id='my run', device=None)

cirq-google/cirq_google/json_test_data/spec.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
'RuntimeInfo',
6868
'SharedRuntimeInfo',
6969
'ExecutableGroupResultFilesystemRecord',
70+
'NaiveQubitPlacer',
7071
]
7172
},
7273
tested_elsewhere=[

cirq-google/cirq_google/workflow/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,8 @@
1919
from cirq_google.workflow.io import (
2020
ExecutableGroupResultFilesystemRecord,
2121
)
22+
23+
from cirq_google.workflow.qubit_placement import (
24+
QubitPlacer,
25+
NaiveQubitPlacer,
26+
)

cirq-google/cirq_google/workflow/quantum_executable_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ def _get_quantum_executables():
138138
return [
139139
QuantumExecutable(
140140
spec=_get_example_spec(name=f'example-program-{i}'),
141+
problem_topology=cirq.LineTopology(10),
141142
circuit=_get_random_circuit(qubits, random_state=i),
142143
measurement=BitstringsMeasurement(n_repetitions=10),
143144
)

cirq-google/cirq_google/workflow/quantum_runtime.py

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,18 @@
1919
from typing import Any, Dict, Optional, List
2020

2121
import cirq
22+
import numpy as np
2223
from cirq import _compat
23-
from cirq.protocols import dataclass_json_dict
24+
from cirq.protocols import dataclass_json_dict, obj_to_dict_helper
2425
from cirq_google.workflow._abstract_engine_processor_shim import AbstractEngineProcessorShim
2526
from cirq_google.workflow.io import _FilesystemSaver
2627
from cirq_google.workflow.progress import _PrintLogger
2728
from cirq_google.workflow.quantum_executable import (
29+
QuantumExecutable,
2830
ExecutableSpec,
2931
QuantumExecutableGroup,
3032
)
33+
from cirq_google.workflow.qubit_placement import QubitPlacer, NaiveQubitPlacer
3134

3235

3336
@dataclasses.dataclass
@@ -39,17 +42,27 @@ class SharedRuntimeInfo:
3942
4043
Args:
4144
run_id: A unique `str` identifier for this run.
45+
device: The actual device used during execution, not just its processor_id
4246
"""
4347

4448
run_id: str
49+
device: Optional[cirq.Device] = None
4550

4651
def _json_dict_(self) -> Dict[str, Any]:
47-
return dataclass_json_dict(self, namespace='cirq.google')
52+
# TODO (gh-4699): serialize `device` as well once SerializableDevice is serializable.
53+
return obj_to_dict_helper(self, attribute_names=['run_id'], namespace='cirq.google')
4854

4955
def __repr__(self) -> str:
5056
return _compat.dataclass_repr(self, namespace='cirq_google')
5157

5258

59+
def _try_tuple(k: Any) -> Any:
60+
"""If we serialize a dictionary that had tuple keys, they get turned to json lists."""
61+
if isinstance(k, list):
62+
return tuple(k)
63+
return k # coverage: ignore
64+
65+
5366
@dataclasses.dataclass
5467
class RuntimeInfo:
5568
"""Runtime information relevant to a particular `cg.QuantumExecutable`.
@@ -59,12 +72,25 @@ class RuntimeInfo:
5972
Args:
6073
execution_index: What order (in its `cg.QuantumExecutableGroup`) this
6174
`cg.QuantumExecutable` was executed.
75+
qubit_placement: If a QubitPlacer was used, a record of the mapping
76+
from problem-qubits to device-qubits.
6277
"""
6378

6479
execution_index: int
80+
qubit_placement: Optional[Dict[Any, cirq.Qid]] = None
6581

6682
def _json_dict_(self) -> Dict[str, Any]:
67-
return dataclass_json_dict(self, namespace='cirq.google')
83+
d = dataclass_json_dict(self, namespace='cirq.google')
84+
if d['qubit_placement']:
85+
d['qubit_placement'] = list(d['qubit_placement'].items())
86+
return d
87+
88+
@classmethod
89+
def _from_json_dict_(cls, **kwargs) -> 'RuntimeInfo':
90+
kwargs.pop('cirq_type')
91+
if kwargs.get('qubit_placement', None):
92+
kwargs['qubit_placement'] = {_try_tuple(k): v for k, v in kwargs['qubit_placement']}
93+
return cls(**kwargs)
6894

6995
def __repr__(self) -> str:
7096
return _compat.dataclass_repr(self, namespace='cirq_google')
@@ -127,10 +153,16 @@ class QuantumRuntimeConfiguration:
127153
run_id: A unique `str` identifier for a run. If data already exists for the specified
128154
`run_id`, an exception will be raised. If not specified, we will generate a UUID4
129155
run identifier.
156+
random_seed: An initial seed to make the run deterministic. Otherwise, the default numpy
157+
seed will be used.
158+
qubit_placer: A `cg.QubitPlacer` implementation to map executable qubits to device qubits.
159+
The placer is only called if a given `cg.QuantumExecutable` has a `problem_topology`.
130160
"""
131161

132162
processor: AbstractEngineProcessorShim
133163
run_id: Optional[str] = None
164+
random_seed: Optional[int] = None
165+
qubit_placer: QubitPlacer = NaiveQubitPlacer()
134166

135167
def _json_dict_(self) -> Dict[str, Any]:
136168
return dataclass_json_dict(self, namespace='cirq.google')
@@ -180,26 +212,38 @@ def execute(
180212
# coverage: ignore
181213
raise ValueError("Please provide a non-empty `base_data_dir`.")
182214

183-
shared_rt_info = SharedRuntimeInfo(run_id=run_id)
215+
sampler = rt_config.processor.get_sampler()
216+
device = rt_config.processor.get_device()
217+
218+
shared_rt_info = SharedRuntimeInfo(
219+
run_id=run_id,
220+
device=device,
221+
)
184222
executable_results = []
185223

186224
saver = _FilesystemSaver(base_data_dir=base_data_dir, run_id=run_id)
187225
saver.initialize(rt_config, shared_rt_info)
188226

189-
sampler = rt_config.processor.get_sampler()
190227
logger = _PrintLogger(n_total=len(executable_group))
191228
logger.initialize()
229+
230+
rs = np.random.RandomState(rt_config.random_seed)
231+
exe: QuantumExecutable
192232
for i, exe in enumerate(executable_group):
193233
runtime_info = RuntimeInfo(execution_index=i)
194234

195235
if exe.params != tuple():
196236
raise NotImplementedError("Circuit params are not yet supported.")
197-
198-
circuit = exe.circuit
199-
200237
if not hasattr(exe.measurement, 'n_repetitions'):
201238
raise NotImplementedError("Only `BitstringsMeasurement` are supported.")
202239

240+
circuit = exe.circuit
241+
if exe.problem_topology is not None:
242+
circuit, mapping = rt_config.qubit_placer.place_circuit(
243+
circuit, problem_topo=exe.problem_topology, shared_rt_info=shared_rt_info, rs=rs
244+
)
245+
runtime_info.qubit_placement = mapping
246+
203247
sampler_run_result = sampler.run(circuit, repetitions=exe.measurement.n_repetitions)
204248

205249
exe_result = ExecutableResult(

cirq-google/cirq_google/workflow/quantum_runtime_test.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,11 @@ def _load_result_by_hand(tmpdir: str, run_id: str) -> cg.ExecutableGroupResult:
158158
@pytest.mark.parametrize('run_id_in', ['unit_test_runid', None])
159159
def test_execute(tmpdir, run_id_in, patch_cirq_default_resolvers):
160160
assert patch_cirq_default_resolvers
161-
rt_config = cg.QuantumRuntimeConfiguration(processor=_MockEngineProcessor(), run_id=run_id_in)
161+
rt_config = cg.QuantumRuntimeConfiguration(
162+
processor=_MockEngineProcessor(),
163+
run_id=run_id_in,
164+
qubit_placer=cg.NaiveQubitPlacer(),
165+
)
162166
executable_group = cg.QuantumExecutableGroup(_get_quantum_executables())
163167
returned_exegroup_result = cg.execute(
164168
rt_config=rt_config, executable_group=executable_group, base_data_dir=tmpdir
@@ -175,5 +179,9 @@ def test_execute(tmpdir, run_id_in, patch_cirq_default_resolvers):
175179
)
176180
exegroup_result: cg.ExecutableGroupResult = egr_record.load(base_data_dir=tmpdir)
177181

182+
# TODO(gh-4699): Don't null-out device once it's serializable.
183+
assert isinstance(returned_exegroup_result.shared_runtime_info.device, cg.SerializableDevice)
184+
returned_exegroup_result.shared_runtime_info.device = None
185+
178186
assert returned_exegroup_result == exegroup_result
179187
assert manual_exegroup_result == exegroup_result
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Copyright 2021 The Cirq Developers
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Features for placing qubits onto devices."""
16+
17+
import abc
18+
import dataclasses
19+
from typing import Dict, Any, Tuple, TYPE_CHECKING
20+
21+
import numpy as np
22+
23+
import cirq
24+
from cirq import _compat
25+
from cirq.devices.named_topologies import NamedTopology
26+
27+
if TYPE_CHECKING:
28+
import cirq_google as cg
29+
30+
31+
class QubitPlacer(metaclass=abc.ABCMeta):
32+
@abc.abstractmethod
33+
def place_circuit(
34+
self,
35+
circuit: cirq.AbstractCircuit,
36+
problem_topo: NamedTopology,
37+
shared_rt_info: 'cg.SharedRuntimeInfo',
38+
rs: np.random.RandomState,
39+
) -> Tuple[cirq.FrozenCircuit, Dict[Any, cirq.Qid]]:
40+
"""Place a circuit with a given topology.
41+
42+
Args:
43+
circuit: The circuit.
44+
problem_topo: The topologies (i.e. connectivity) of the circuit.
45+
shared_rt_info: A `cg.SharedRuntimeInfo` object that may contain additional info
46+
to inform placement.
47+
rs: A `RandomState` to enable pseudo-random placement strategies.
48+
"""
49+
50+
51+
@dataclasses.dataclass(frozen=True)
52+
class NaiveQubitPlacer(QubitPlacer):
53+
"""Don't do any qubit placement, use circuit qubits."""
54+
55+
def place_circuit(
56+
self,
57+
circuit: cirq.AbstractCircuit,
58+
problem_topo: NamedTopology,
59+
shared_rt_info: 'cg.SharedRuntimeInfo',
60+
rs: np.random.RandomState,
61+
) -> Tuple[cirq.FrozenCircuit, Dict[Any, cirq.Qid]]:
62+
return circuit.freeze(), {q: q for q in circuit.all_qubits()}
63+
64+
def _json_dict_(self) -> Dict[str, Any]:
65+
return cirq.dataclass_json_dict(self, namespace='cirq.google')
66+
67+
def __repr__(self) -> str:
68+
return _compat.dataclass_repr(self, namespace='cirq_google')

0 commit comments

Comments
 (0)