-
Notifications
You must be signed in to change notification settings - Fork 1k
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
GridDevice gateset, gate_duration, and compilation_target_gateset support #5315
Changes from 1 commit
ff8aebc
486376e
84bdfe5
17262f0
288a4dc
8135a8c
7a1bcd9
e9de7ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -16,9 +16,15 @@ | |||||||
|
||||||||
import re | ||||||||
|
||||||||
from typing import Any, Set, Tuple, cast | ||||||||
from typing import Any, Dict, List, Sequence, Set, Tuple, Type, Union, cast | ||||||||
import warnings | ||||||||
|
||||||||
import cirq | ||||||||
from cirq_google import ops | ||||||||
from cirq_google import transformers | ||||||||
from cirq_google.api import v2 | ||||||||
from cirq_google.experimental import ops as experimental_ops | ||||||||
from cirq_google.ops.fsim_gate_family import POSSIBLE_FSIM_GATES | ||||||||
|
||||||||
|
||||||||
def _validate_device_specification(proto: v2.device_pb2.DeviceSpecification) -> None: | ||||||||
|
@@ -77,6 +83,91 @@ def _validate_device_specification(proto: v2.device_pb2.DeviceSpecification) -> | |||||||
) | ||||||||
|
||||||||
|
||||||||
def _build_gateset_and_gate_durations( | ||||||||
proto: v2.device_pb2.DeviceSpecification, | ||||||||
) -> Tuple[cirq.Gateset, Dict[cirq.GateFamily, cirq.Duration]]: | ||||||||
gates_list: List[Union[Type[cirq.Gate], cirq.Gate, cirq.GateFamily]] = [] | ||||||||
fsim_gates: List[Union[Type[POSSIBLE_FSIM_GATES], POSSIBLE_FSIM_GATES]] = [] | ||||||||
gate_durations: Dict[cirq.GateFamily, cirq.Duration] = {} | ||||||||
|
||||||||
# TODO(#5050) Describe how to add/remove gates. | ||||||||
|
||||||||
for gate_spec in proto.valid_gates: | ||||||||
gate_name = gate_spec.WhichOneof('gate') | ||||||||
cirq_gates: List[Union[Type[cirq.Gate], cirq.Gate, cirq.GateFamily]] = [] | ||||||||
|
||||||||
if gate_name == 'syc': | ||||||||
cirq_gates = [ops.SYC] | ||||||||
fsim_gates.append(ops.SYC) | ||||||||
elif gate_name == 'sqrt_iswap': | ||||||||
cirq_gates = [cirq.SQRT_ISWAP] | ||||||||
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. We should not push The gate durations should also be specified for the corresponding fsim gate family; since we are accepting all equivalents operations across types; instead of specifying it only for 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. Different FSimGates have different durations today: Cirq/cirq-google/cirq_google/devices/known_devices.py Lines 304 to 306 in 9459df7
And in general, each gate type in Since gate duration is purely informational, IMO it's OK to not have all the equivalent forms of a gate as part of the key. The alternative solution of having duration under the FSimGateFamily and taking the min/max/some other aggregate of the durations of all the different gates necessarily loses some information. 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.
The purpose of a gateset is description and validation. Having redundant gates makes description more confusing and verbose, which should be avoided.
We can insert a separate fsim gate family instance for each fsim gate type in DeviceSpecification; so we can associate gate duration with the corresponding fsim gate family. i.e. # gate durations dict should contain this.
{cirq.FSimGateFamily(gates_to_accept=[cirq.SQRT_ISWAP]): 32_000,
cirq.FSimGateFamily(gates_to_accept=[cg.SYC]): 12_000}
# instead of .
{cirq.GateFamily(cirq.SQRT_ISWAP): 32_000,
cirq.GateFamily(cg.SYC): 12_000} In general, we should be consistent with the gate families that we are using for description and validation, so that inconsistencies like the following cannot occur: $> sqrt_iswap_gate = cirq.FSimGate(-np.pi/4, 0)
# Returns True because of validation across types by FSimGateFamily.
$> assert sqrt_iswap_gate in sqrt_iswap_metadata.gateset
# Should return True but will return False right now because `sqrt_iswap_gate in cirq.GateFamily(cirq.SQRT_ISWAP)` is False
$> assert any(sqrt_iswap_gate in gf for gf in sqrt_iswap_metadata.gate_durations) 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. 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. Actually your approach currently fails 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. Synced offline. Will change the gatesets to have a separate FSimGateFamily for each 2q gate instead of a single FSimGateFamily. 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. Updated. I considered changing the test to a more black-box approach of checking whether certain gates belong in the gateset rather than asserting via gateset equality, but decided against it because we do want to test the exact gateset elements to verify its string representation. |
||||||||
fsim_gates.append(cirq.SQRT_ISWAP) | ||||||||
elif gate_name == 'sqrt_iswap_inv': | ||||||||
cirq_gates = [cirq.SQRT_ISWAP_INV] | ||||||||
fsim_gates.append(cirq.SQRT_ISWAP_INV) | ||||||||
elif gate_name == 'cz': | ||||||||
cirq_gates = [cirq.CZ] | ||||||||
fsim_gates.append(cirq.CZ) | ||||||||
elif gate_name == 'phased_xz': | ||||||||
cirq_gates = [ | ||||||||
cirq.PhasedXZGate, | ||||||||
cirq.XPowGate, | ||||||||
cirq.YPowGate, | ||||||||
cirq.ZPowGate, | ||||||||
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. Should we add a For example: What happens if the proto specification includes 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. Yeah the acceptance of ZPowGates should definitely be controlled entirely by For the case where the spec includes |
||||||||
cirq.PhasedXPowGate, | ||||||||
] | ||||||||
elif gate_name == 'virtual_zpow': | ||||||||
cirq_gates = [cirq.GateFamily(cirq.ZPowGate, tags_to_ignore=[ops.PhysicalZTag()])] | ||||||||
elif gate_name == 'physical_zpow': | ||||||||
cirq_gates = [cirq.GateFamily(cirq.ZPowGate, tags_to_accept=[ops.PhysicalZTag()])] | ||||||||
elif gate_name == 'coupler_pulse': | ||||||||
cirq_gates = [experimental_ops.CouplerPulse] | ||||||||
elif gate_name == 'meas': | ||||||||
cirq_gates = [cirq.MeasurementGate] | ||||||||
elif gate_name == 'wait': | ||||||||
cirq_gates = [cirq.WaitGate] | ||||||||
else: | ||||||||
# coverage: ignore | ||||||||
warnings.warn( | ||||||||
f"The DeviceSpecification contains the gate '{gate_name}' which is not recognized" | ||||||||
" by Cirq and will be ignored. This may be due to an out-of-date Cirq version.", | ||||||||
UserWarning, | ||||||||
) | ||||||||
continue | ||||||||
|
||||||||
gates_list.extend(cirq_gates) | ||||||||
for g in cirq_gates: | ||||||||
if not isinstance(g, cirq.GateFamily): | ||||||||
g = cirq.GateFamily(g) | ||||||||
gate_durations[g] = cirq.Duration(picos=gate_spec.gate_duration_picos) | ||||||||
|
||||||||
if fsim_gates: | ||||||||
gates_list.append(ops.FSimGateFamily(gates_to_accept=fsim_gates)) | ||||||||
|
||||||||
# TODO(#4833) Add identity gate support | ||||||||
# TODO(#5050) Add GlobalPhaseGate support | ||||||||
|
||||||||
return cirq.Gateset(*gates_list), gate_durations | ||||||||
|
||||||||
|
||||||||
def _build_compilation_target_gatesets( | ||||||||
gateset: cirq.Gateset, | ||||||||
) -> Sequence[cirq.CompilationTargetGateset]: | ||||||||
"""Detects compilation target gatesets based on what gates are inside the gateset.""" | ||||||||
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. So, if we support multiple gatesets, do we have a way to decompose to a combination of CZ and sqrt-iswap for instance? Or do we have to pick one? I think picking one is probably fine, since most grids will likely only support one type of gate, but we should make this clear in the documentation. 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. Yep we would have to pick one. If there's a use case to compile to both CZ and sqrt-iswap in the future we could revisit; doing so now for every target would probably create a combinatorial explosion of target gatesets. On the other hand, if a device supports both CZ and sqrt-iswap, and someone wants to compile a circuit containing sqrt-iswap + some other arbitrary 2q gates using a CZ target gateset, the intended behavior is to leave sqrt-iswap gates untouched and compile other 2q gates to CZ + 1q gates. This isn't supported yet by existing target gatesets, but I'm planning to change that. Will clarify this behavior in documentation. |
||||||||
|
||||||||
target_gatesets: List[cirq.CompilationTargetGateset] = [] | ||||||||
if cirq.CZ in gateset: | ||||||||
target_gatesets.append(cirq.CZTargetGateset()) | ||||||||
if ops.SYC in gateset: | ||||||||
target_gatesets.append(transformers.SycamoreTargetGateset()) | ||||||||
if cirq.SQRT_ISWAP in gateset: | ||||||||
target_gatesets.append( | ||||||||
cirq.SqrtIswapTargetGateset(use_sqrt_iswap_inv=cirq.SQRT_ISWAP_INV in gateset) | ||||||||
) | ||||||||
verult marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
return tuple(target_gatesets) | ||||||||
|
||||||||
|
||||||||
@cirq.value_equality | ||||||||
class GridDevice(cirq.Device): | ||||||||
"""Device object representing Google devices with a grid qubit layout. | ||||||||
|
@@ -122,7 +213,16 @@ class GridDevice(cirq.Device): | |||||||
* Get a collection of approximate gate durations for every gate supported by the device. | ||||||||
>>> device.metadata.gate_durations | ||||||||
|
||||||||
TODO(#5050) Add compilation_target_gatesets example. | ||||||||
* Get a collection of valid CompilationTargetGatesets for the device, which can be used to | ||||||||
transform a circuit which is invalid for the device to a valid one. | ||||||||
verult marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
>>> device.metadata.compilation_target_gatesets | ||||||||
|
||||||||
* Assuming valid CompilationTargetGatesets exist for the device, select the first one and | ||||||||
use it to transform a circuit to an equivalent form which is valid for the device. | ||||||||
>>> cirq.optimize_for_target_gateset( | ||||||||
circuit, | ||||||||
gateset=device.metadata.compilation_target_gatesets[0] | ||||||||
) | ||||||||
|
||||||||
Notes for cirq_google internal implementation: | ||||||||
|
||||||||
|
@@ -187,12 +287,15 @@ def from_proto(cls, proto: v2.device_pb2.DeviceSpecification) -> 'GridDevice': | |||||||
if len(target.ids) == 2 and ts.target_ordering == v2.device_pb2.TargetSet.SYMMETRIC | ||||||||
] | ||||||||
|
||||||||
# TODO(#5050) implement gate durations | ||||||||
gateset, gate_durations = _build_gateset_and_gate_durations(proto) | ||||||||
|
||||||||
try: | ||||||||
metadata = cirq.GridDeviceMetadata( | ||||||||
qubit_pairs=qubit_pairs, | ||||||||
gateset=cirq.Gateset(), # TODO(#5050) implement | ||||||||
gateset=gateset, | ||||||||
gate_durations=gate_durations, | ||||||||
all_qubits=all_qubits, | ||||||||
compilation_target_gatesets=_build_compilation_target_gatesets(gateset), | ||||||||
) | ||||||||
except ValueError as ve: # coverage: ignore | ||||||||
# Spec errors should have been caught in validation above. | ||||||||
|
@@ -219,9 +322,9 @@ def validate_operation(self, operation: cirq.Operation) -> None: | |||||||
Raises: | ||||||||
ValueError: The operation isn't valid for this device. | ||||||||
""" | ||||||||
# TODO(#5050) uncomment once gateset logic is implemented | ||||||||
# if operation not in self._metadata.gateset: | ||||||||
# raise ValueError(f'Operation {operation} is not a supported gate') | ||||||||
|
||||||||
if operation not in self._metadata.gateset: | ||||||||
raise ValueError(f'Operation {operation} is not a supported gate') | ||||||||
verult marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
for q in operation.qubits: | ||||||||
if q not in self._metadata.qubit_set: | ||||||||
|
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.
This is a private method, but I would still add a docstring since it's a little long.