Skip to content

Commit f3b7a82

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

15 files changed

+206
-13
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
@@ -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_runtime.py

Lines changed: 50 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,16 +42,24 @@ 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

58+
def _try_tuple(k:Any)-> Any:
59+
"""If we serialize a dictionary that had tuple keys, they get turned to json lists."""
60+
if isinstance(k, list):
61+
return tuple(k)
62+
return k
5263

5364
@dataclasses.dataclass
5465
class RuntimeInfo:
@@ -59,12 +70,25 @@ class RuntimeInfo:
5970
Args:
6071
execution_index: What order (in its `cg.QuantumExecutableGroup`) this
6172
`cg.QuantumExecutable` was executed.
73+
qubit_placement: If a QubitPlacer was used, a record of the mapping
74+
from problem-qubits to device-qubits.
6275
"""
6376

6477
execution_index: int
78+
qubit_placement: Optional[Dict[Any, cirq.Qid]] = None
6579

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

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

132160
processor: AbstractEngineProcessorShim
133161
run_id: Optional[str] = None
162+
random_seed: Optional[int] = None
163+
qubit_placer: QubitPlacer = NaiveQubitPlacer()
134164

135165
def _json_dict_(self) -> Dict[str, Any]:
136166
return dataclass_json_dict(self, namespace='cirq.google')
@@ -180,26 +210,38 @@ def execute(
180210
# coverage: ignore
181211
raise ValueError("Please provide a non-empty `base_data_dir`.")
182212

183-
shared_rt_info = SharedRuntimeInfo(run_id=run_id)
213+
sampler = rt_config.processor.get_sampler()
214+
device = rt_config.processor.get_device()
215+
216+
shared_rt_info = SharedRuntimeInfo(
217+
run_id=run_id,
218+
device=device,
219+
)
184220
executable_results = []
185221

186222
saver = _FilesystemSaver(base_data_dir=base_data_dir, run_id=run_id)
187223
saver.initialize(rt_config, shared_rt_info)
188224

189-
sampler = rt_config.processor.get_sampler()
190225
logger = _PrintLogger(n_total=len(executable_group))
191226
logger.initialize()
227+
228+
rs = np.random.RandomState(rt_config.random_seed)
229+
exe: QuantumExecutable
192230
for i, exe in enumerate(executable_group):
193231
runtime_info = RuntimeInfo(execution_index=i)
194232

195233
if exe.params != tuple():
196234
raise NotImplementedError("Circuit params are not yet supported.")
197-
198-
circuit = exe.circuit
199-
200235
if not hasattr(exe.measurement, 'n_repetitions'):
201236
raise NotImplementedError("Only `BitstringsMeasurement` are supported.")
202237

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

205247
exe_result = ExecutableResult(

cirq-google/cirq_google/workflow/quantum_runtime_test.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,5 +175,9 @@ def test_execute(tmpdir, run_id_in, patch_cirq_default_resolvers):
175175
)
176176
exegroup_result: cg.ExecutableGroupResult = egr_record.load(base_data_dir=tmpdir)
177177

178+
# TODO(gh-4699): Don't null-out device once it's serializable.
179+
assert isinstance(returned_exegroup_result.shared_runtime_info.device, cg.SerializableDevice)
180+
returned_exegroup_result.shared_runtime_info.device = None
181+
178182
assert returned_exegroup_result == exegroup_result
179183
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')
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_topo=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)