Skip to content

(De-)serialization of CircuitOperations #3923

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

Merged
merged 23 commits into from
May 24, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
185e95e
First pass, still needs tests.
95-martin-orion Mar 8, 2021
c80d982
Clean up TODOs
95-martin-orion Mar 9, 2021
1ee4b30
Basic (de-)serialization tests.
95-martin-orion Mar 9, 2021
fdaa216
Update gate_sets_test.
95-martin-orion Mar 9, 2021
4aa8ccf
Deserializer test coverage
95-martin-orion Mar 11, 2021
14ca867
CircuitOpSerializer test coverage
95-martin-orion Mar 11, 2021
de2b042
SerializableGateSet test coverage
95-martin-orion Mar 11, 2021
fded376
Known devices test coverage
95-martin-orion Mar 11, 2021
7f3a269
Formatting
95-martin-orion Mar 11, 2021
1877a71
Pick up changes in proto.
95-martin-orion Mar 17, 2021
8b2ed14
Rewind gate_sets_test format.
95-martin-orion Mar 17, 2021
b98219d
Avoid deprecated fields
95-martin-orion Mar 17, 2021
c3f5215
Merge remote-tracking branch 'upstream/master' into cirq-proto-serial-2
95-martin-orion Mar 22, 2021
69b7168
Update cirq.google refs
95-martin-orion Mar 22, 2021
138441b
Round out tests and docs
95-martin-orion Apr 12, 2021
f6020d4
Merge branch 'master' into cirq-proto-serial-2
95-martin-orion Apr 12, 2021
4759ff2
Use map for raw_constants in serializer
95-martin-orion Apr 12, 2021
c717ef9
Fix test typo
95-martin-orion Apr 12, 2021
0a90658
Support and test for subcircuits in device spec.
95-martin-orion Apr 20, 2021
a37dccb
Merge branch 'master' into cirq-proto-serial-2
95-martin-orion Apr 20, 2021
b7ab8d6
Docstring and deprecation fixes
95-martin-orion May 19, 2021
c40d061
Merge branch 'master' into cirq-proto-serial-2
95-martin-orion May 19, 2021
233fe88
Merge branch 'master' into cirq-proto-serial-2
CirqBot May 24, 2021
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
2 changes: 2 additions & 0 deletions cirq/google/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,13 @@
)

from cirq.google.op_deserializer import (
CircuitOpDeserializer,
DeserializingArg,
GateOpDeserializer,
)

from cirq.google.op_serializer import (
CircuitOpSerializer,
GateOpSerializer,
SerializingArg,
)
Expand Down
11 changes: 11 additions & 0 deletions cirq/google/common_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,11 @@ def _can_serialize_limited_iswap(exponent: float):
op_wrapper=lambda op, proto: _add_phase_match(op, proto),
)

#############################################
#
# Miscellaneous serializers and deserializers
#
#############################################

#
# WaitGate serializer and deserializer
Expand Down Expand Up @@ -675,3 +680,9 @@ def _can_serialize_limited_iswap(exponent: float):
],
num_qubits_param='num_qubits',
)

#
# CircuitOperation serializer and deserializer
#
CIRCUIT_OP_SERIALIZER = op_serializer.CircuitOpSerializer()
CIRCUIT_OP_DESERIALIZER = op_deserializer.CircuitOpDeserializer()
14 changes: 10 additions & 4 deletions cirq/google/devices/known_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from cirq._doc import document
from cirq.devices import GridQubit
from cirq.google import gate_sets, serializable_gate_set
from cirq.google import gate_sets, op_serializer, serializable_gate_set
from cirq.google.api import v2
from cirq.google.api.v2 import device_pb2
from cirq.google.devices.serializable_device import SerializableDevice
Expand Down Expand Up @@ -132,9 +132,9 @@ def create_device_proto_for_qubits(
gs_proto = out.valid_gate_sets.add()
gs_proto.name = gate_set.gate_set_name
gate_ids: Set[str] = set()
for gate_type in gate_set.serializers:
for serializer in gate_set.serializers[gate_type]:
gate_id = serializer.serialized_gate_id
for internal_type in gate_set.serializers:
for serializer in gate_set.serializers[internal_type]:
gate_id = serializer.serialized_id
if gate_id in gate_ids:
# Only add each type once
continue
Expand All @@ -143,7 +143,13 @@ def create_device_proto_for_qubits(
gate = gs_proto.valid_gates.add()
gate.id = gate_id

if not isinstance(serializer, op_serializer.GateOpSerializer):
# This implies that 'serializer' handles non-gate ops,
# such as CircuitOperations. No other properties apply.
continue

# Choose target set and number of qubits based on gate type.
gate_type = internal_type

# Note: if it is not a measurement gate and doesn't inherit
# from SingleQubitGate, it's assumed to be a two qubit gate.
Expand Down
52 changes: 52 additions & 0 deletions cirq/google/devices/known_devices_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,58 @@ def test_sycamore_grid_layout():
cirq.google.Sycamore23.validate_operation(sqrt_iswap)


def test_proto_with_circuitop():
circuitop_gateset = cirq.google.serializable_gate_set.SerializableGateSet(
gate_set_name='circuitop_gateset',
serializers=[cgc.CIRCUIT_OP_SERIALIZER],
deserializers=[cgc.CIRCUIT_OP_DESERIALIZER],
)
circuitop_proto = cirq.google.devices.known_devices.create_device_proto_from_diagram(
"aa\naa",
[circuitop_gateset],
)

assert (
str(circuitop_proto)
== """\
valid_gate_sets {
Copy link
Collaborator

Choose a reason for hiding this comment

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

One thing that we should think about is whether a device specification should specify that the device supports circuit operations or not. (Or should we assume they all support circuit operations?)

This may have to be in a later PR, since it involves further proto changes, but we should give it some thought and maybe add an issue about it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Apart from the transitional phase where we're still bringing support online, I think assuming all devices support circuit operations is valid, since in the worst case you can simply decompose the circuit operation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

TODO(95-martin-orion): test creating a device spec with CircuitOperation to identify necessary changes / ensure that this is well-behaved.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@dstrain115: added test_sycamore_circuitop_device to provide this coverage, and it failed. Updated serializable_device to resolve the issue.

name: "circuitop_gateset"
valid_gates {
id: "circuit"
}
}
valid_qubits: "0_0"
valid_qubits: "0_1"
valid_qubits: "1_0"
valid_qubits: "1_1"
valid_targets {
name: "meas_targets"
target_ordering: SUBSET_PERMUTATION
}
valid_targets {
name: "2_qubit_targets"
target_ordering: SYMMETRIC
targets {
ids: "0_0"
ids: "0_1"
}
targets {
ids: "0_0"
ids: "1_0"
}
targets {
ids: "0_1"
ids: "1_1"
}
targets {
ids: "1_0"
ids: "1_1"
}
}
"""
)


def test_proto_with_waitgate():
wait_gateset = cirq.google.serializable_gate_set.SerializableGateSet(
gate_set_name='wait_gateset',
Expand Down
18 changes: 9 additions & 9 deletions cirq/google/devices/serializable_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,20 +169,20 @@ def from_proto(
# Loop through serializers and map gate_definitions to type
gates_by_type: Dict[Type['cirq.Gate'], List[_GateDefinition]] = {}
for gate_set in gate_sets:
for gate_type in gate_set.supported_gate_types():
for serializer in gate_set.serializers[gate_type]:
gate_id = serializer.serialized_gate_id
if gate_id not in gate_definitions:
for internal_type in gate_set.supported_internal_types():
for serializer in gate_set.serializers[internal_type]:
serialized_id = serializer.serialized_id
if serialized_id not in gate_definitions:
raise ValueError(
f'Serializer has {gate_id} which is not supported '
f'Serializer has {serialized_id} which is not supported '
'by the device specification'
)
if gate_type not in gates_by_type:
gates_by_type[gate_type] = []
gate_def = gate_definitions[gate_id].with_can_serialize_predicate(
if internal_type not in gates_by_type:
gates_by_type[internal_type] = []
gate_def = gate_definitions[serialized_id].with_can_serialize_predicate(
serializer.can_serialize_predicate
)
gates_by_type[gate_type].append(gate_def)
gates_by_type[internal_type].append(gate_def)

return SerializableDevice(
qubits=[cls._qid_from_str(q) for q in proto.valid_qubits],
Expand Down
2 changes: 2 additions & 0 deletions cirq/google/json_test_data/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
],
should_not_be_serialized=[
'AnnealSequenceSearchStrategy',
'CircuitOpDeserializer',
'CircuitOpSerializer',
'CircuitWithCalibration',
'ConvertToSqrtIswapGates',
'ConvertToSycamoreGates',
Expand Down
147 changes: 134 additions & 13 deletions cirq/google/op_deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
)
from dataclasses import dataclass

import abc
import sympy

from cirq import circuits
from cirq._compat import deprecated
from cirq.google import arg_func_langs
from cirq.google.api import v2
from cirq.google.ops.calibration_tag import CalibrationTag
Expand All @@ -31,6 +36,34 @@
import cirq


class OpDeserializer(abc.ABC):
"""Generic supertype for op deserializers."""

@property
@abc.abstractmethod
def serialized_id(self) -> str:
"""Returns the string identifier for the resulting serialized object.

This value should reflect the internal_type of the serializer.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I am not sure I understand what this sentence means. What does "should reflect the internal_type" imply?
Same comment for deserializer.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I must have misunderstood the IDs when typing this up (plus a copy-paste error in the deserializer comment). Updated to (hopefully) more accurately represent the behavior.

"""

@property # type: ignore
@deprecated(deadline='v0.12', fix='Use serialized_id instead.')
def serialized_gate_id(self) -> str:
return self.serialized_id

@abc.abstractmethod
def from_proto(
self,
proto,
*,
arg_function_language: str = '',
constants: List[v2.program_pb2.Constant] = None,
raw_constants: List[Any] = None,
) -> 'cirq.Operation':
pass


@dataclass(frozen=True)
class DeserializingArg:
"""Specification of the arguments to deserialize an argument to a gate.
Expand All @@ -57,7 +90,7 @@ class DeserializingArg:
default: Any = None


class GateOpDeserializer:
class GateOpDeserializer(OpDeserializer):
"""Describes how to deserialize a proto to a given Gate type.

Attributes:
Expand Down Expand Up @@ -94,28 +127,33 @@ def __init__(
deserialize_tokens: Whether to convert tokens to
CalibrationTags. Defaults to True.
"""
self.serialized_gate_id = serialized_gate_id
self.gate_constructor = gate_constructor
self.args = args
self.num_qubits_param = num_qubits_param
self.op_wrapper = op_wrapper
self.deserialize_tokens = deserialize_tokens
self._serialized_gate_id = serialized_gate_id
self._gate_constructor = gate_constructor
self._args = args
self._num_qubits_param = num_qubits_param
self._op_wrapper = op_wrapper
self._deserialize_tokens = deserialize_tokens

@property
def serialized_id(self):
return self._serialized_gate_id

def from_proto(
self,
proto: v2.program_pb2.Operation,
*,
arg_function_language: str = '',
constants: List[v2.program_pb2.Constant] = None,
raw_constants: List[Any] = None, # unused
) -> 'cirq.Operation':
"""Turns a cirq.google.api.v2.Operation proto into a GateOperation."""
qubits = [v2.qubit_from_proto_id(q.id) for q in proto.qubits]
args = self._args_from_proto(proto, arg_function_language=arg_function_language)
if self.num_qubits_param is not None:
args[self.num_qubits_param] = len(qubits)
gate = self.gate_constructor(**args)
op = self.op_wrapper(gate.on(*qubits), proto)
if self.deserialize_tokens:
if self._num_qubits_param is not None:
args[self._num_qubits_param] = len(qubits)
gate = self._gate_constructor(**args)
op = self._op_wrapper(gate.on(*qubits), proto)
if self._deserialize_tokens:
which = proto.WhichOneof('token')
if which == 'token_constant_index':
if not constants:
Expand All @@ -135,7 +173,7 @@ def _args_from_proto(
self, proto: v2.program_pb2.Operation, *, arg_function_language: str
) -> Dict[str, arg_func_langs.ARG_LIKE]:
return_args = {}
for arg in self.args:
for arg in self._args:
if arg.serialized_name not in proto.args:
if arg.default:
return_args[arg.constructor_arg_name] = arg.default
Expand All @@ -158,3 +196,86 @@ def _args_from_proto(
if value is not None:
return_args[arg.constructor_arg_name] = value
return return_args


class CircuitOpDeserializer(OpDeserializer):
"""Describes how to serialize CircuitOperations."""

@property
def serialized_id(self):
return 'circuit'

def from_proto(
self,
proto: v2.program_pb2.CircuitOperation,
*,
arg_function_language: str = '',
constants: List[v2.program_pb2.Constant] = None,
raw_constants: List[Any] = None,
) -> 'cirq.CircuitOperation':
"""Turns a cirq.google.api.v2.CircuitOperation proto into a CircuitOperation."""
if constants is None or raw_constants is None:
raise ValueError(
'CircuitOp deserialization requires a constants list and a corresponding list of '
'post-deserialization values (raw_constants).'
)
circuit = raw_constants[proto.circuit_constant_index]
if not isinstance(circuit, circuits.FrozenCircuit):
raise ValueError(
f'Constant at index {proto.circuit_constant_index} was expected to be a circuit, '
f'but it has type {type(circuit)} in the raw_constants list.'
)

which_rep_spec = proto.repetition_specification.WhichOneof('repetition_value')
if which_rep_spec == "repetition_count":
rep_ids = None
repetitions = proto.repetition_specification.repetition_count
elif which_rep_spec == "repetition_ids":
rep_ids = proto.repetition_specification.repetition_ids.ids
repetitions = len(rep_ids)
else:
rep_ids = None
repetitions = 1

qubit_map = {
v2.qubit_from_proto_id(entry.key.id): v2.qubit_from_proto_id(entry.value.id)
for entry in proto.qubit_map.entries
}
measurement_key_map = {
entry.key.string_key: entry.value.string_key
for entry in proto.measurement_key_map.entries
}
arg_map = {
arg_func_langs.arg_from_proto(
entry.key, arg_function_language=arg_function_language
): arg_func_langs.arg_from_proto(
entry.value, arg_function_language=arg_function_language
)
for entry in proto.arg_map.entries
}

for arg in arg_map.keys():
if not isinstance(arg, (str, sympy.Symbol)):
print('whoopee')
raise ValueError(
'Invalid key parameter type in deserialized CircuitOperation. '
f'Expected str or sympy.Symbol, found {type(arg)}.'
f'\nFull arg: {arg}'
)

for arg in arg_map.values():
if not isinstance(arg, (str, sympy.Symbol, float, int)):
raise ValueError(
'Invalid value parameter type in deserialized CircuitOperation. '
f'Expected str, sympy.Symbol, or number; found {type(arg)}.'
f'\nFull arg: {arg}'
)

return circuits.CircuitOperation(
circuit,
repetitions,
qubit_map,
measurement_key_map,
arg_map, # type: ignore
rep_ids,
)
Loading