Skip to content
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

[runtime] QubitPlacer #4678

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cirq-core/cirq/devices/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ def can_add_operation_into_moment(
"""
return not moment.operates_on(operation.qubits)

def get_nx_graph(self):
import cirq.contrib.routing as ccr

return ccr.gridqubits_to_graph_device(self.qubit_set())


@value.value_equality
class SymmetricalQidPair:
Expand Down
2 changes: 2 additions & 0 deletions cirq-google/cirq_google/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@
ExecutableGroupResultFilesystemRecord,
QuantumRuntimeConfiguration,
execute,
QubitPlacer,
NaiveQubitPlacer,
)

from cirq_google import experimental
Expand Down
14 changes: 14 additions & 0 deletions cirq-google/cirq_google/devices/serializable_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,20 @@ def __init__(
def qubit_set(self) -> FrozenSet[cirq.Qid]:
return frozenset(self.qubits)

def _json_dict_(self):
# TODO gate_definitions
return cirq.protocols.obj_to_dict_helper(self, attribute_names=['qubits'])

@classmethod
def _from_json_dict_(cls, qubits, **kwargs):
# TODO: gate_definitions
return cls(qubits=qubits, gate_definitions={})

def __eq__(self, other):
if not isinstance(other, SerializableDevice):
return False
return (self.qubits, self.gate_definitions) == (other.qubits, other.gate_definitions)

@classmethod
def from_proto(
cls,
Expand Down
2 changes: 2 additions & 0 deletions cirq-google/cirq_google/json_resolver_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,6 @@ def _class_resolver_dictionary() -> Dict[str, ObjectFactory]:
'cirq.google.ExecutableGroupResultFilesystemRecord': cirq_google.ExecutableGroupResultFilesystemRecord,
# pylint: enable=line-too-long
'cirq.google.QuantumRuntimeConfiguration': cirq_google.QuantumRuntimeConfiguration,
'cirq.google.NaiveQubitPlacer': cirq_google.NaiveQubitPlacer,
'SerializableDevice': cirq_google.SerializableDevice,
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,31 @@
},
"runtime_info": {
"cirq_type": "cirq.google.RuntimeInfo",
"execution_index": 5
"execution_index": 5,
"qubit_placement": [
[
[
0,
0
],
{
"cirq_type": "GridQubit",
"row": 5,
"col": 5
}
],
[
[
1,
1
],
{
"cirq_type": "GridQubit",
"row": 6,
"col": 6
}
]
]
},
"raw_data": {
"cirq_type": "Result",
Expand Down

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)
1 change: 1 addition & 0 deletions cirq-google/cirq_google/json_test_data/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
'RuntimeInfo',
'SharedRuntimeInfo',
'ExecutableGroupResultFilesystemRecord',
'NaiveQubitPlacer',
]
},
tested_elsewhere=[
Expand Down
5 changes: 5 additions & 0 deletions cirq-google/cirq_google/workflow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@
from cirq_google.workflow.io import (
ExecutableGroupResultFilesystemRecord,
)

from cirq_google.workflow.qubit_placement import (
QubitPlacer,
NaiveQubitPlacer,
)
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ def _get_quantum_executables():
return [
QuantumExecutable(
spec=_get_example_spec(name=f'example-program-{i}'),
problem_topology=cirq.LineTopology(10),
circuit=_get_random_circuit(qubits, random_state=i),
measurement=BitstringsMeasurement(n_repetitions=10),
)
Expand Down
60 changes: 52 additions & 8 deletions cirq-google/cirq_google/workflow/quantum_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`.
Expand All @@ -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')
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
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(
Expand Down
10 changes: 9 additions & 1 deletion cirq-google/cirq_google/workflow/quantum_runtime_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,11 @@ def _load_result_by_hand(tmpdir: str, run_id: str) -> cg.ExecutableGroupResult:
@pytest.mark.parametrize('run_id_in', ['unit_test_runid', None])
def test_execute(tmpdir, run_id_in, patch_cirq_default_resolvers):
assert patch_cirq_default_resolvers
rt_config = cg.QuantumRuntimeConfiguration(processor=_MockEngineProcessor(), run_id=run_id_in)
rt_config = cg.QuantumRuntimeConfiguration(
processor=_MockEngineProcessor(),
run_id=run_id_in,
qubit_placer=cg.NaiveQubitPlacer(),
)
executable_group = cg.QuantumExecutableGroup(_get_quantum_executables())
returned_exegroup_result = cg.execute(
rt_config=rt_config, executable_group=executable_group, base_data_dir=tmpdir
Expand All @@ -179,5 +183,9 @@ def test_execute(tmpdir, run_id_in, patch_cirq_default_resolvers):
)
exegroup_result: cg.ExecutableGroupResult = egr_record.load(base_data_dir=tmpdir)

# TODO(gh-4699): Don't null-out device once it's serializable.
assert isinstance(returned_exegroup_result.shared_runtime_info.device, cg.SerializableDevice)
returned_exegroup_result.shared_runtime_info.device = None

assert returned_exegroup_result == exegroup_result
assert manual_exegroup_result == exegroup_result
Loading