Skip to content

Commit c0fabc5

Browse files
mpharriganrht
authored andcommitted
[runtime] QubitPlacer part 1 (quantumlib#4700)
Splitting up quantumlib#4678 - The interface: `QubitPlacer` - The fallback: `NaiveQubitPlacer` - runtime hooks some hacks around quantumlib#4699
1 parent a80cb96 commit c0fabc5

16 files changed

+221
-14
lines changed

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
@@ -55,4 +55,5 @@ def _class_resolver_dictionary() -> Dict[str, ObjectFactory]:
5555
'cirq.google.ExecutableGroupResultFilesystemRecord': cirq_google.ExecutableGroupResultFilesystemRecord,
5656
# pylint: enable=line-too-long
5757
'cirq.google.QuantumRuntimeConfiguration': cirq_google.QuantumRuntimeConfiguration,
58+
'cirq.google.NaiveQubitPlacer': cirq_google.NaiveQubitPlacer,
5859
}

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
@@ -66,6 +66,7 @@
6666
'RuntimeInfo',
6767
'SharedRuntimeInfo',
6868
'ExecutableGroupResultFilesystemRecord',
69+
'NaiveQubitPlacer',
6970
]
7071
},
7172
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,21 +42,31 @@ 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
@classmethod
4752
def _json_namespace_(cls) -> str:
4853
return 'cirq.google'
4954

5055
def _json_dict_(self) -> Dict[str, Any]:
51-
return dataclass_json_dict(self)
56+
# TODO (gh-4699): serialize `device` as well once SerializableDevice is serializable.
57+
return obj_to_dict_helper(self, attribute_names=['run_id'])
5258

5359
def __repr__(self) -> str:
5460
return _compat.dataclass_repr(self, namespace='cirq_google')
5561

5662

63+
def _try_tuple(k: Any) -> Any:
64+
"""If we serialize a dictionary that had tuple keys, they get turned to json lists."""
65+
if isinstance(k, list):
66+
return tuple(k)
67+
return k # coverage: ignore
68+
69+
5770
@dataclasses.dataclass
5871
class RuntimeInfo:
5972
"""Runtime information relevant to a particular `cg.QuantumExecutable`.
@@ -63,16 +76,29 @@ class RuntimeInfo:
6376
Args:
6477
execution_index: What order (in its `cg.QuantumExecutableGroup`) this
6578
`cg.QuantumExecutable` was executed.
79+
qubit_placement: If a QubitPlacer was used, a record of the mapping
80+
from problem-qubits to device-qubits.
6681
"""
6782

6883
execution_index: int
84+
qubit_placement: Optional[Dict[Any, cirq.Qid]] = None
6985

7086
@classmethod
7187
def _json_namespace_(cls) -> str:
7288
return 'cirq.google'
7389

7490
def _json_dict_(self) -> Dict[str, Any]:
75-
return dataclass_json_dict(self)
91+
d = dataclass_json_dict(self)
92+
if d['qubit_placement']:
93+
d['qubit_placement'] = list(d['qubit_placement'].items())
94+
return d
95+
96+
@classmethod
97+
def _from_json_dict_(cls, **kwargs) -> 'RuntimeInfo':
98+
kwargs.pop('cirq_type')
99+
if kwargs.get('qubit_placement', None):
100+
kwargs['qubit_placement'] = {_try_tuple(k): v for k, v in kwargs['qubit_placement']}
101+
return cls(**kwargs)
76102

77103
def __repr__(self) -> str:
78104
return _compat.dataclass_repr(self, namespace='cirq_google')
@@ -143,10 +169,16 @@ class QuantumRuntimeConfiguration:
143169
run_id: A unique `str` identifier for a run. If data already exists for the specified
144170
`run_id`, an exception will be raised. If not specified, we will generate a UUID4
145171
run identifier.
172+
random_seed: An initial seed to make the run deterministic. Otherwise, the default numpy
173+
seed will be used.
174+
qubit_placer: A `cg.QubitPlacer` implementation to map executable qubits to device qubits.
175+
The placer is only called if a given `cg.QuantumExecutable` has a `problem_topology`.
146176
"""
147177

148178
processor: AbstractEngineProcessorShim
149179
run_id: Optional[str] = None
180+
random_seed: Optional[int] = None
181+
qubit_placer: QubitPlacer = NaiveQubitPlacer()
150182

151183
@classmethod
152184
def _json_namespace_(cls) -> str:
@@ -200,26 +232,38 @@ def execute(
200232
# coverage: ignore
201233
raise ValueError("Please provide a non-empty `base_data_dir`.")
202234

203-
shared_rt_info = SharedRuntimeInfo(run_id=run_id)
235+
sampler = rt_config.processor.get_sampler()
236+
device = rt_config.processor.get_device()
237+
238+
shared_rt_info = SharedRuntimeInfo(
239+
run_id=run_id,
240+
device=device,
241+
)
204242
executable_results = []
205243

206244
saver = _FilesystemSaver(base_data_dir=base_data_dir, run_id=run_id)
207245
saver.initialize(rt_config, shared_rt_info)
208246

209-
sampler = rt_config.processor.get_sampler()
210247
logger = _PrintLogger(n_total=len(executable_group))
211248
logger.initialize()
249+
250+
rs = np.random.RandomState(rt_config.random_seed)
251+
exe: QuantumExecutable
212252
for i, exe in enumerate(executable_group):
213253
runtime_info = RuntimeInfo(execution_index=i)
214254

215255
if exe.params != tuple():
216256
raise NotImplementedError("Circuit params are not yet supported.")
217-
218-
circuit = exe.circuit
219-
220257
if not hasattr(exe.measurement, 'n_repetitions'):
221258
raise NotImplementedError("Only `BitstringsMeasurement` are supported.")
222259

260+
circuit = exe.circuit
261+
if exe.problem_topology is not None:
262+
circuit, mapping = rt_config.qubit_placer.place_circuit(
263+
circuit, problem_topology=exe.problem_topology, shared_rt_info=shared_rt_info, rs=rs
264+
)
265+
runtime_info.qubit_placement = mapping
266+
223267
sampler_run_result = sampler.run(circuit, repetitions=exe.measurement.n_repetitions)
224268

225269
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
@@ -162,7 +162,11 @@ def _load_result_by_hand(tmpdir: str, run_id: str) -> cg.ExecutableGroupResult:
162162
@pytest.mark.parametrize('run_id_in', ['unit_test_runid', None])
163163
def test_execute(tmpdir, run_id_in, patch_cirq_default_resolvers):
164164
assert patch_cirq_default_resolvers
165-
rt_config = cg.QuantumRuntimeConfiguration(processor=_MockEngineProcessor(), run_id=run_id_in)
165+
rt_config = cg.QuantumRuntimeConfiguration(
166+
processor=_MockEngineProcessor(),
167+
run_id=run_id_in,
168+
qubit_placer=cg.NaiveQubitPlacer(),
169+
)
166170
executable_group = cg.QuantumExecutableGroup(_get_quantum_executables())
167171
returned_exegroup_result = cg.execute(
168172
rt_config=rt_config, executable_group=executable_group, base_data_dir=tmpdir
@@ -179,5 +183,9 @@ def test_execute(tmpdir, run_id_in, patch_cirq_default_resolvers):
179183
)
180184
exegroup_result: cg.ExecutableGroupResult = egr_record.load(base_data_dir=tmpdir)
181185

186+
# TODO(gh-4699): Don't null-out device once it's serializable.
187+
assert isinstance(returned_exegroup_result.shared_runtime_info.device, cg.SerializableDevice)
188+
returned_exegroup_result.shared_runtime_info.device = None
189+
182190
assert returned_exegroup_result == exegroup_result
183191
assert manual_exegroup_result == exegroup_result
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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+
26+
if TYPE_CHECKING:
27+
import cirq_google as cg
28+
29+
30+
class QubitPlacer(metaclass=abc.ABCMeta):
31+
@abc.abstractmethod
32+
def place_circuit(
33+
self,
34+
circuit: cirq.AbstractCircuit,
35+
problem_topology: 'cirq.NamedTopology',
36+
shared_rt_info: 'cg.SharedRuntimeInfo',
37+
rs: np.random.RandomState,
38+
) -> Tuple['cirq.FrozenCircuit', Dict[Any, 'cirq.Qid']]:
39+
"""Place a circuit with a given topology.
40+
41+
Args:
42+
circuit: The circuit.
43+
problem_topology: The topologies (i.e. connectivity) of the circuit.
44+
shared_rt_info: A `cg.SharedRuntimeInfo` object that may contain additional info
45+
to inform placement.
46+
rs: A `RandomState` to enable pseudo-random placement strategies.
47+
48+
Returns:
49+
A tuple of a new frozen circuit with the qubits placed and a mapping from input
50+
qubits or nodes to output qubits.
51+
"""
52+
53+
54+
@dataclasses.dataclass(frozen=True)
55+
class NaiveQubitPlacer(QubitPlacer):
56+
"""Don't do any qubit placement, use circuit qubits."""
57+
58+
def place_circuit(
59+
self,
60+
circuit: 'cirq.AbstractCircuit',
61+
problem_topology: 'cirq.NamedTopology',
62+
shared_rt_info: 'cg.SharedRuntimeInfo',
63+
rs: np.random.RandomState,
64+
) -> Tuple['cirq.FrozenCircuit', Dict[Any, 'cirq.Qid']]:
65+
return circuit.freeze(), {q: q for q in circuit.all_qubits()}
66+
67+
@classmethod
68+
def _json_namespace_(cls) -> str:
69+
return 'cirq.google'
70+
71+
def _json_dict_(self) -> Dict[str, Any]:
72+
return cirq.dataclass_json_dict(self)
73+
74+
def __repr__(self) -> str:
75+
return _compat.dataclass_repr(self, namespace='cirq_google')
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
import cirq
16+
import cirq_google as cg
17+
18+
import numpy as np
19+
20+
21+
def test_naive_qubit_placer():
22+
topo = cirq.TiltedSquareLattice(4, 2)
23+
qubits = sorted(topo.nodes_to_gridqubits(offset=(5, 3)).values())
24+
circuit = cirq.experiments.random_rotations_between_grid_interaction_layers_circuit(
25+
qubits, depth=8, two_qubit_op_factory=lambda a, b, _: cirq.SQRT_ISWAP(a, b)
26+
)
27+
28+
assert all(q in cg.Sycamore23.qubit_set() for q in circuit.all_qubits())
29+
30+
qp = cg.NaiveQubitPlacer()
31+
circuit2, mapping = qp.place_circuit(
32+
circuit,
33+
problem_topology=topo,
34+
shared_rt_info=cg.SharedRuntimeInfo(run_id='1'),
35+
rs=np.random.RandomState(1),
36+
)
37+
assert circuit is not circuit2
38+
assert circuit == circuit2
39+
assert all(q in cg.Sycamore23.qubit_set() for q in circuit.all_qubits())
40+
for k, v in mapping.items():
41+
assert k == v

0 commit comments

Comments
 (0)