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

Add optional qubit_set property to cirq.Device #2605

Merged
merged 11 commits into from
Dec 17, 2019
13 changes: 10 additions & 3 deletions cirq/contrib/graph_device/graph_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
import abc
import itertools

from typing import Iterable, Optional
from typing import Iterable, Optional, FrozenSet, TYPE_CHECKING, Tuple, cast

from cirq import devices, ops, value

from cirq.contrib.graph_device.hypergraph import UndirectedHypergraph

if TYPE_CHECKING:
import cirq


class UndirectedGraphDeviceEdge(metaclass=abc.ABCMeta):
"""An edge of an undirected graph device.
Expand Down Expand Up @@ -147,8 +150,12 @@ def __init__(self,
self.crosstalk_graph = crosstalk_graph

@property
def qubits(self):
return tuple(sorted(self.device_graph.vertices))
def qubits(self) -> Tuple['cirq.Qid', ...]:
return cast(Tuple['cirq.Qid', ...],
tuple(sorted(self.device_graph.vertices)))

def qubit_set(self) -> FrozenSet['cirq.Qid']:
return frozenset(self.qubits)

@property
def edges(self):
Expand Down
10 changes: 10 additions & 0 deletions cirq/contrib/graph_device/graph_device_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,13 @@ def test_graph_device_copy_and_add():
device_copy += device_addend
assert device != device_copy
assert device_copy == device_sum


def test_qubit_set():
a, b, c, d = cirq.LineQubit.range(4)
device_graph = ccgd.UndirectedHypergraph(labelled_edges={
(a, b): None,
(c, d): None
})
device = ccgd.UndirectedGraphDevice(device_graph=device_graph)
assert device.qubit_set() == {a, b, c, d}
35 changes: 29 additions & 6 deletions cirq/devices/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,44 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import TYPE_CHECKING

import abc

from typing import TYPE_CHECKING, Optional, AbstractSet

if TYPE_CHECKING:
import cirq

# Note: circuit/schedule types specified by name to avoid circular references.


class Device(metaclass=abc.ABCMeta):
"""Hardware constraints for validating circuits."""

def qubit_set(self) -> Optional[AbstractSet['cirq.Qid']]:
"""Returns a set or frozenset of qubits on the device, if possible.

This method returns None when the set of qubits is unavailable. For
example, `cirq.UnconstrainedDevice` returns `None` because allows any
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant with the Returns statement. I would probably keep one or the other rather than repeating it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

and all qubits; it has no finite qubit set to return.

Returns:
If the device has a finite set of qubits, then a set or frozen set
of all qubits on the device is returned.

If the device has no well defined finite set of qubits (e.g.
`cirq.UnconstrainedDevice` has this property), then `None` is
returned.
"""

# Compatibility hack to work with devices that were written before this
# method was defined.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @dstrain115 mentioned, it'd be nice to have a plan for where we are going with Device so that we can converge on an interface in the future and get rid of this hack. Maybe create an issue to track this and reference the issue number here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged.

for name in ['qubits', '_qubits']:
if hasattr(self, name):
val = getattr(self, name)
if callable(val):
val = val()
return frozenset(val)

# Default to the qubits being unknown.
return None

def decompose_operation(self, operation: 'cirq.Operation'
) -> 'cirq.OP_TREE':
"""Returns a device-valid decomposition for the given operation.
Expand All @@ -36,7 +60,6 @@ def decompose_operation(self, operation: 'cirq.Operation'
"""
return operation

@abc.abstractmethod
def validate_operation(self, operation: 'cirq.Operation') -> None:
"""Raises an exception if an operation is not valid.

Expand Down
41 changes: 41 additions & 0 deletions cirq/devices/device_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import pytest

import cirq


def test_qubit_set():

class RawDevice(cirq.Device):
pass

assert RawDevice().qubit_set() is None

class QubitFieldDevice(cirq.Device):

def __init__(self):
self.qubits = cirq.LineQubit.range(3)

assert QubitFieldDevice().qubit_set() == frozenset(cirq.LineQubit.range(3))

class PrivateQubitFieldDevice(cirq.Device):

def __init__(self):
self._qubits = cirq.LineQubit.range(4)

assert PrivateQubitFieldDevice().qubit_set() == frozenset(
cirq.LineQubit.range(4))

class QubitMethodDevice(cirq.Device):

def qubits(self):
return cirq.LineQubit.range(5)

assert QubitMethodDevice().qubit_set() == frozenset(cirq.LineQubit.range(5))

class PrivateQubitMethodDevice(cirq.Device):

def qubits(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be _qubits

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return cirq.LineQubit.range(6)

assert PrivateQubitMethodDevice().qubit_set() == frozenset(
cirq.LineQubit.range(6))
6 changes: 3 additions & 3 deletions cirq/devices/unconstrained_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@
class _UnconstrainedDevice(device.Device):
"""A device that allows everything, infinitely fast."""

def qubit_set(self) -> None:
return None

def duration_of(self, operation):
return value.Duration(picos=0)

def validate_operation(self, operation):
pass

def validate_moment(self, moment):
pass

Expand Down
4 changes: 4 additions & 0 deletions cirq/devices/unconstrained_device_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ def test_repr():
def test_infinitely_fast():
assert cirq.UNCONSTRAINED_DEVICE.duration_of(cirq.X(
cirq.NamedQubit('a'))) == cirq.Duration(picos=0)


def test_qubit_set():
assert cirq.UNCONSTRAINED_DEVICE.qubit_set() is None
28 changes: 7 additions & 21 deletions cirq/google/devices/serializable_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,8 @@
# limitations under the License.
"""Device object for converting from device specification protos"""

from typing import (
Callable,
cast,
Dict,
Iterable,
Optional,
List,
Set,
Tuple,
Type,
TYPE_CHECKING,
)
from typing import (Callable, cast, Dict, Iterable, Optional, List, Set, Tuple,
Type, TYPE_CHECKING, FrozenSet)

from cirq import devices, ops
from cirq.google import serializable_gate_set
Expand Down Expand Up @@ -107,19 +97,15 @@ def __init__(

Args:
qubits: A list of valid Qid for the device.
durations: A dictionary with keys as Gate Types to the duration
of operations with that Gate type.
target_sets: The valid targets that a gate can act on. This is
passed as a dictionary with keys as the Gate Type. The values
are a set of valid targets (arguments) to that gate. These
are tuples of Qids. For instance, for 2-qubit gates, they
will be pairs of Qubits.
permutation_gates: A list of types that act on all permutations
of the qubit targets. (e.g. measurement gates)
gate_definitions: Maps cirq gates to device properties for that
gate.
"""
self.qubits = qubits
self.gate_definitions = gate_definitions

def qubit_set(self) -> FrozenSet['cirq.Qid']:
return frozenset(self.qubits)

@classmethod
def from_proto(
cls, proto: v2.device_pb2.DeviceSpecification,
Expand Down
1 change: 1 addition & 0 deletions cirq/google/devices/serializable_device_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def test_foxtail():
foxtail = cg.SerializableDevice.from_proto(
proto=cg.devices.known_devices.FOXTAIL_PROTO,
gate_sets=[cg.gate_sets.XMON])
assert foxtail.qubit_set() == frozenset(cirq.GridQubit.rect(2, 11, 0, 0))
foxtail.validate_operation(cirq.X(valid_qubit1))
foxtail.validate_operation(cirq.X(valid_qubit2))
foxtail.validate_operation(cirq.X(valid_qubit3))
Expand Down
5 changes: 4 additions & 1 deletion cirq/google/devices/xmon_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import cast, Iterable, List, Optional, Set, TYPE_CHECKING
from typing import cast, Iterable, List, Optional, Set, TYPE_CHECKING, FrozenSet

from cirq import circuits, devices, ops, protocols, value
from cirq.google.optimizers import convert_to_xmon_gates
Expand Down Expand Up @@ -44,6 +44,9 @@ def __init__(self, measurement_duration: 'cirq.DURATION_LIKE',
self._exp_z_duration = value.Duration(exp_11_duration)
self.qubits = frozenset(qubits)

def qubit_set(self) -> FrozenSet['cirq.GridQubit']:
return self.qubits

def decompose_operation(self,
operation: 'cirq.Operation') -> 'cirq.OP_TREE':
return convert_to_xmon_gates.ConvertToXmonGates().convert(operation)
Expand Down
4 changes: 4 additions & 0 deletions cirq/google/devices/xmon_device_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,7 @@ def test_row_and_col():
cirq.GridQubit(5, 1),
cirq.GridQubit(6, 1)
]


def test_qubit_set():
assert cg.Foxtail.qubit_set() == frozenset(cirq.GridQubit.rect(2, 11, 0, 0))
5 changes: 4 additions & 1 deletion cirq/ion/ion_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import cast, Iterable, Optional, Set, TYPE_CHECKING
from typing import cast, Iterable, Optional, Set, TYPE_CHECKING, FrozenSet

from cirq import circuits, value, devices, ops, protocols
from cirq.ion import convert_to_ion_gates
Expand Down Expand Up @@ -46,6 +46,9 @@ def __init__(self, measurement_duration: 'cirq.DURATION_LIKE',
self._oneq_gates_duration = value.Duration(oneq_gates_duration)
self.qubits = frozenset(qubits)

def qubit_set(self) -> FrozenSet['cirq.LineQubit']:
return self.qubits

def decompose_operation(self, operation: ops.Operation) -> ops.OP_TREE:
return convert_to_ion_gates.ConvertToIonGates().convert_one(operation)

Expand Down
4 changes: 4 additions & 0 deletions cirq/ion/ion_device_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,7 @@ def test_at():
assert d.at(-1) is None
assert d.at(0) == cirq.LineQubit(0)
assert d.at(2) == cirq.LineQubit(2)


def test_qubit_set():
assert ion_device(3).qubit_set() == frozenset(cirq.LineQubit.range(3))
5 changes: 4 additions & 1 deletion cirq/neutral_atoms/neutral_atom_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import itertools
import collections
from typing import Iterable, cast, DefaultDict, TYPE_CHECKING
from typing import Iterable, cast, DefaultDict, TYPE_CHECKING, FrozenSet
from numpy import sqrt
from cirq import devices, ops, circuits, value
from cirq.devices.grid_qubit import GridQubit
Expand Down Expand Up @@ -73,6 +73,9 @@ def __init__(self, measurement_duration: 'cirq.DURATION_LIKE',
raise ValueError('Unsupported qubit type: {!r}'.format(q))
self.qubits = frozenset(qubits)

def qubit_set(self) -> FrozenSet['cirq.GridQubit']:
return self.qubits

def qubit_list(self):
return [qubit for qubit in self.qubits]

Expand Down
5 changes: 5 additions & 0 deletions cirq/neutral_atoms/neutral_atom_devices_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,8 @@ def test_str():
│ │
(1, 0)───(1, 1)
""".strip()


def test_qubit_set():
assert square_device(2, 2).qubit_set() == frozenset(
cirq.GridQubit.square(2, 0, 0))
2 changes: 1 addition & 1 deletion docs/devices.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ from cirq.devices import GridQubit
class Xmon10Device(cirq.Device):

def __init__(self):
self.qubits = [GridQubit(i, 0) for i in range(10)]
self._qubits = [GridQubit(i, 0) for i in range(10)]

def validate_operation(self, operation):
if not isinstance(operation, cirq.GateOperation):
Expand Down