-
Notifications
You must be signed in to change notification settings - Fork 1.1k
[runtime] QubitPlacer part 1 #4700
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f95de7f
b1d42bd
15a5e84
ae79851
ea8282b
fa0345d
ee3b78e
f864215
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"cirq_type": "cirq.google.NaiveQubitPlacer" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
cirq_google.NaiveQubitPlacer() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
{ | ||
"cirq_type": "cirq.google.RuntimeInfo", | ||
"execution_index": 5 | ||
"execution_index": 5, | ||
"qubit_placement": null | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
cirq_google.RuntimeInfo(execution_index=5) | ||
cirq_google.RuntimeInfo(execution_index=5, qubit_placement=None) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
cirq_google.SharedRuntimeInfo(run_id='my run') | ||
cirq_google.SharedRuntimeInfo(run_id='my run', device=None) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,15 +19,18 @@ | |
from typing import Any, Dict, Optional, List | ||
|
||
import cirq | ||
import numpy as np | ||
from cirq import _compat | ||
from cirq.protocols import dataclass_json_dict | ||
from cirq.protocols import dataclass_json_dict, obj_to_dict_helper | ||
from cirq_google.workflow._abstract_engine_processor_shim import AbstractEngineProcessorShim | ||
from cirq_google.workflow.io import _FilesystemSaver | ||
from cirq_google.workflow.progress import _PrintLogger | ||
from cirq_google.workflow.quantum_executable import ( | ||
QuantumExecutable, | ||
ExecutableSpec, | ||
QuantumExecutableGroup, | ||
) | ||
from cirq_google.workflow.qubit_placement import QubitPlacer, NaiveQubitPlacer | ||
|
||
|
||
@dataclasses.dataclass | ||
|
@@ -39,21 +42,31 @@ class SharedRuntimeInfo: | |
|
||
Args: | ||
run_id: A unique `str` identifier for this run. | ||
device: The actual device used during execution, not just its processor_id | ||
""" | ||
|
||
run_id: str | ||
device: Optional[cirq.Device] = None | ||
|
||
@classmethod | ||
def _json_namespace_(cls) -> str: | ||
return 'cirq.google' | ||
|
||
def _json_dict_(self) -> Dict[str, Any]: | ||
return dataclass_json_dict(self) | ||
# TODO (gh-4699): serialize `device` as well once SerializableDevice is serializable. | ||
return obj_to_dict_helper(self, attribute_names=['run_id']) | ||
|
||
def __repr__(self) -> str: | ||
return _compat.dataclass_repr(self, namespace='cirq_google') | ||
|
||
|
||
def _try_tuple(k: Any) -> Any: | ||
"""If we serialize a dictionary that had tuple keys, they get turned to json lists.""" | ||
if isinstance(k, list): | ||
return tuple(k) | ||
return k # coverage: ignore | ||
|
||
|
||
@dataclasses.dataclass | ||
class RuntimeInfo: | ||
"""Runtime information relevant to a particular `cg.QuantumExecutable`. | ||
|
@@ -63,16 +76,29 @@ class RuntimeInfo: | |
Args: | ||
execution_index: What order (in its `cg.QuantumExecutableGroup`) this | ||
`cg.QuantumExecutable` was executed. | ||
qubit_placement: If a QubitPlacer was used, a record of the mapping | ||
from problem-qubits to device-qubits. | ||
""" | ||
|
||
execution_index: int | ||
qubit_placement: Optional[Dict[Any, cirq.Qid]] = None | ||
|
||
@classmethod | ||
def _json_namespace_(cls) -> str: | ||
return 'cirq.google' | ||
|
||
def _json_dict_(self) -> Dict[str, Any]: | ||
return dataclass_json_dict(self) | ||
d = dataclass_json_dict(self) | ||
if d['qubit_placement']: | ||
d['qubit_placement'] = list(d['qubit_placement'].items()) | ||
return d | ||
|
||
@classmethod | ||
def _from_json_dict_(cls, **kwargs) -> 'RuntimeInfo': | ||
kwargs.pop('cirq_type') | ||
if kwargs.get('qubit_placement', None): | ||
kwargs['qubit_placement'] = {_try_tuple(k): v for k, v in kwargs['qubit_placement']} | ||
return cls(**kwargs) | ||
|
||
def __repr__(self) -> str: | ||
return _compat.dataclass_repr(self, namespace='cirq_google') | ||
|
@@ -143,10 +169,16 @@ class QuantumRuntimeConfiguration: | |
run_id: A unique `str` identifier for a run. If data already exists for the specified | ||
`run_id`, an exception will be raised. If not specified, we will generate a UUID4 | ||
run identifier. | ||
random_seed: An initial seed to make the run deterministic. Otherwise, the default numpy | ||
seed will be used. | ||
qubit_placer: A `cg.QubitPlacer` implementation to map executable qubits to device qubits. | ||
The placer is only called if a given `cg.QuantumExecutable` has a `problem_topology`. | ||
""" | ||
|
||
processor: AbstractEngineProcessorShim | ||
run_id: Optional[str] = None | ||
random_seed: Optional[int] = None | ||
qubit_placer: QubitPlacer = NaiveQubitPlacer() | ||
|
||
@classmethod | ||
def _json_namespace_(cls) -> str: | ||
|
@@ -200,26 +232,38 @@ def execute( | |
# coverage: ignore | ||
raise ValueError("Please provide a non-empty `base_data_dir`.") | ||
|
||
shared_rt_info = SharedRuntimeInfo(run_id=run_id) | ||
sampler = rt_config.processor.get_sampler() | ||
device = rt_config.processor.get_device() | ||
|
||
shared_rt_info = SharedRuntimeInfo( | ||
run_id=run_id, | ||
device=device, | ||
) | ||
executable_results = [] | ||
|
||
saver = _FilesystemSaver(base_data_dir=base_data_dir, run_id=run_id) | ||
saver.initialize(rt_config, shared_rt_info) | ||
|
||
sampler = rt_config.processor.get_sampler() | ||
logger = _PrintLogger(n_total=len(executable_group)) | ||
logger.initialize() | ||
|
||
rs = np.random.RandomState(rt_config.random_seed) | ||
exe: QuantumExecutable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this declaration mess with the below loop ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is how you do type annotations for loop variables There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this annotation? Wouldn't the type of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pycharm wasn't picking it up |
||
for i, exe in enumerate(executable_group): | ||
runtime_info = RuntimeInfo(execution_index=i) | ||
|
||
if exe.params != tuple(): | ||
raise NotImplementedError("Circuit params are not yet supported.") | ||
|
||
circuit = exe.circuit | ||
|
||
if not hasattr(exe.measurement, 'n_repetitions'): | ||
raise NotImplementedError("Only `BitstringsMeasurement` are supported.") | ||
|
||
circuit = exe.circuit | ||
if exe.problem_topology is not None: | ||
circuit, mapping = rt_config.qubit_placer.place_circuit( | ||
circuit, problem_topology=exe.problem_topology, shared_rt_info=shared_rt_info, rs=rs | ||
) | ||
runtime_info.qubit_placement = mapping | ||
|
||
sampler_run_result = sampler.run(circuit, repetitions=exe.measurement.n_repetitions) | ||
|
||
exe_result = ExecutableResult( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# 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. | ||
|
||
"""Features for placing qubits onto devices.""" | ||
|
||
import abc | ||
import dataclasses | ||
from typing import Dict, Any, Tuple, TYPE_CHECKING | ||
|
||
import numpy as np | ||
|
||
import cirq | ||
from cirq import _compat | ||
|
||
if TYPE_CHECKING: | ||
import cirq_google as cg | ||
|
||
|
||
class QubitPlacer(metaclass=abc.ABCMeta): | ||
@abc.abstractmethod | ||
def place_circuit( | ||
self, | ||
circuit: cirq.AbstractCircuit, | ||
problem_topology: 'cirq.NamedTopology', | ||
shared_rt_info: 'cg.SharedRuntimeInfo', | ||
rs: np.random.RandomState, | ||
) -> Tuple['cirq.FrozenCircuit', Dict[Any, 'cirq.Qid']]: | ||
"""Place a circuit with a given topology. | ||
|
||
Args: | ||
circuit: The circuit. | ||
problem_topology: The topologies (i.e. connectivity) of the circuit. | ||
shared_rt_info: A `cg.SharedRuntimeInfo` object that may contain additional info | ||
to inform placement. | ||
rs: A `RandomState` to enable pseudo-random placement strategies. | ||
mpharrigan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Returns: | ||
A tuple of a new frozen circuit with the qubits placed and a mapping from input | ||
qubits or nodes to output qubits. | ||
""" | ||
|
||
|
||
@dataclasses.dataclass(frozen=True) | ||
mpharrigan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
class NaiveQubitPlacer(QubitPlacer): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename the class to a more descriptive name. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you have a suggestion There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's not really a "no op", it's more like an identity |
||
"""Don't do any qubit placement, use circuit qubits.""" | ||
|
||
def place_circuit( | ||
self, | ||
circuit: 'cirq.AbstractCircuit', | ||
problem_topology: 'cirq.NamedTopology', | ||
shared_rt_info: 'cg.SharedRuntimeInfo', | ||
rs: np.random.RandomState, | ||
) -> Tuple['cirq.FrozenCircuit', Dict[Any, 'cirq.Qid']]: | ||
return circuit.freeze(), {q: q for q in circuit.all_qubits()} | ||
|
||
@classmethod | ||
def _json_namespace_(cls) -> str: | ||
return 'cirq.google' | ||
|
||
def _json_dict_(self) -> Dict[str, Any]: | ||
return cirq.dataclass_json_dict(self) | ||
|
||
def __repr__(self) -> str: | ||
return _compat.dataclass_repr(self, namespace='cirq_google') |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# 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 cirq | ||
import cirq_google as cg | ||
|
||
import numpy as np | ||
|
||
|
||
def test_naive_qubit_placer(): | ||
topo = cirq.TiltedSquareLattice(4, 2) | ||
qubits = sorted(topo.nodes_to_gridqubits(offset=(5, 3)).values()) | ||
circuit = cirq.experiments.random_rotations_between_grid_interaction_layers_circuit( | ||
qubits, depth=8, two_qubit_op_factory=lambda a, b, _: cirq.SQRT_ISWAP(a, b) | ||
) | ||
|
||
assert all(q in cg.Sycamore23.qubit_set() for q in circuit.all_qubits()) | ||
|
||
qp = cg.NaiveQubitPlacer() | ||
circuit2, mapping = qp.place_circuit( | ||
circuit, | ||
problem_topology=topo, | ||
shared_rt_info=cg.SharedRuntimeInfo(run_id='1'), | ||
rs=np.random.RandomState(1), | ||
) | ||
assert circuit is not circuit2 | ||
assert circuit == circuit2 | ||
assert all(q in cg.Sycamore23.qubit_set() for q in circuit.all_qubits()) | ||
for k, v in mapping.items(): | ||
assert k == v |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Should we give the seed to the
rt_config
or the randomstate generator itself ? It seems like down the line if we kept creatingrandomState
s off of this seed we'd get a lot of duplicate random numbers.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
two part answer
rt_config
needs to be serializable and put in a database so we can groupby and agg the results. Seed is (much!) easier than the internal state of a random state