Skip to content

JSON Protocol #1880

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 60 commits into from
Aug 24, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
24655a2
JSON Protocol
mpharrigan Jul 25, 2019
7d03b0a
Allow extensibility
mpharrigan Jul 26, 2019
f558974
Add to GridQubit
mpharrigan Jul 26, 2019
4c1d6e0
Explicit resolvers and improved docstrings
mpharrigan Jul 29, 2019
890155e
[json] percolate functions to top level
mpharrigan Jul 31, 2019
14c910f
Merge remote-tracking branch 'origin/master' into json-protocol
mpharrigan Aug 16, 2019
8dd3880
Automated testing
mpharrigan Aug 16, 2019
9363243
JSON support for Gate
mpharrigan Aug 16, 2019
cfb7df3
More JSON goodness
mpharrigan Aug 16, 2019
f16914f
rename to TEST_OBJECTS
mpharrigan Aug 16, 2019
c3985b3
Automatic testing of every class importable from cirq
mpharrigan Aug 19, 2019
537925f
Test singletons too
mpharrigan Aug 19, 2019
5ded473
Everything to support cirq.Circuit
mpharrigan Aug 19, 2019
6053846
Distinguish between things we dont want to serialize
mpharrigan Aug 19, 2019
01dfd8a
clean up
mpharrigan Aug 19, 2019
bd7713f
Merge remote-tracking branch 'origin/master' into json-protocol
mpharrigan Aug 19, 2019
ae84e61
CSWAP fix has been merged in
mpharrigan Aug 19, 2019
2e57f60
Implement serialization for NamedQubit
mpharrigan Aug 19, 2019
6e5f817
Implement serialization for PhasedXPowGate
mpharrigan Aug 19, 2019
33d446a
remove outdated note
mpharrigan Aug 19, 2019
5af0fde
give this assertion its own test
mpharrigan Aug 19, 2019
7230890
Format
mpharrigan Aug 20, 2019
aaaa514
Pylint
mpharrigan Aug 20, 2019
fbddd6c
Types
mpharrigan Aug 20, 2019
7450e01
Merge remote-tracking branch 'origin/master' into json-protocol
mpharrigan Aug 20, 2019
46ea3a1
There's been a new type
mpharrigan Aug 20, 2019
dc1d5e3
Imports for mypy
mpharrigan Aug 20, 2019
e40bed2
mypy hoops
mpharrigan Aug 20, 2019
0cf6e64
extreme hoops to get both formatter and mypy happy
mpharrigan Aug 20, 2019
9fb07fe
Full coverage
mpharrigan Aug 20, 2019
34a4869
Format
mpharrigan Aug 20, 2019
c24b5fb
Pylint
mpharrigan Aug 20, 2019
e78db5a
Mypy
mpharrigan Aug 20, 2019
86894f3
Test coverage for default ser/deser code paths
mpharrigan Aug 20, 2019
160bd99
coverage
mpharrigan Aug 20, 2019
9180b0e
Put extra imports behind `if TYPE_CHECKING`
mpharrigan Aug 22, 2019
3e55ac7
Merge remote-tracking branch 'origin/master' into json-protocol
mpharrigan Aug 22, 2019
8a05060
Keep up with new public classes being added
mpharrigan Aug 22, 2019
87e511f
Tack on support for sympy.Symbol
mpharrigan Aug 22, 2019
abb0cba
Namespace 3rd party objects
mpharrigan Aug 22, 2019
b64eb9e
self._num_qubits -> self.num_qubits()
mpharrigan Aug 22, 2019
b2e8daa
remove superflous comment
mpharrigan Aug 22, 2019
ce9e684
Document CirqEncoder
mpharrigan Aug 22, 2019
d804b58
Document DEFAULT_RESOLVERS
mpharrigan Aug 22, 2019
40a8c13
Format
mpharrigan Aug 22, 2019
9f36322
Merge remote-tracking branch 'origin/master' into json-protocol
mpharrigan Aug 22, 2019
23c79f6
Messed up the merge a bit
mpharrigan Aug 22, 2019
f8df124
More changes due to breaking change
mpharrigan Aug 22, 2019
9cce33d
Missed this one
mpharrigan Aug 22, 2019
66d05de
deser -> deserialization
mpharrigan Aug 23, 2019
fa94654
read_json -> cirq.read_json
mpharrigan Aug 23, 2019
155d063
invert control
mpharrigan Aug 23, 2019
b51de61
Help developers with an error message
mpharrigan Aug 23, 2019
6e83989
format
mpharrigan Aug 23, 2019
2ba7fbf
Merge remote-tracking branch 'origin/master' into json-protocol
mpharrigan Aug 23, 2019
67ad592
Document to_json
mpharrigan Aug 23, 2019
b98e78a
head explosion emoji
mpharrigan Aug 23, 2019
bbbaa95
manual format
mpharrigan Aug 23, 2019
1034435
skip coverage
mpharrigan Aug 23, 2019
264faf9
Merge branch 'master' into json-protocol
dabacon Aug 24, 2019
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: 4 additions & 1 deletion cirq/devices/grid_qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from typing import Dict, List, Tuple

from cirq import ops
from cirq import ops, protocols


class GridQubit(ops.Qid):
Expand Down Expand Up @@ -139,6 +139,9 @@ def __repr__(self):
def __str__(self):
return '({}, {})'.format(self.row, self.col)

def _json_dict_(self):
return protocols.to_json_dict(self, ['row', 'col'])

def __add__(self, other: Tuple[int, int]) -> 'GridQubit':
if not (isinstance(other, tuple) and len(other) == 2 and
all(isinstance(x, int) for x in other)):
Expand Down
10 changes: 10 additions & 0 deletions cirq/devices/grid_qubit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,13 @@ def test_from_proto_bad_dict():
cirq.GridQubit.from_proto_dict({})
with pytest.raises(ValueError):
cirq.GridQubit.from_proto_dict({'nothing': 1})


def test_to_json():
q = cirq.GridQubit(5, 6)
d = q._json_dict_()
assert d == {
'cirq_type': 'GridQubit',
'row': 5,
'col': 6,
}
5 changes: 4 additions & 1 deletion cirq/line/line_qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import functools
from typing import List

from cirq import ops
from cirq import ops, protocols

@functools.total_ordering
class LineQubit(ops.Qid):
Expand Down Expand Up @@ -84,3 +84,6 @@ def __rsub__(self, other: int) -> 'LineQubit':

def __neg__(self) -> 'LineQubit':
return LineQubit(-self.x)

def _json_dict_(self):
return protocols.to_json_dict(self, ['x'])
7 changes: 7 additions & 0 deletions cirq/line/line_qubit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,10 @@ def test_addition_subtraction_type_error():

def test_neg():
assert -cirq.LineQubit(1) == cirq.LineQubit(-1)


def test_json_dict():
assert cirq.LineQubit(5)._json_dict_() == {
'cirq_type': 'LineQubit',
'x': 5,
}
7 changes: 7 additions & 0 deletions cirq/ops/eigen_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ def __init__(self, *, # Forces keyword args.
def exponent(self) -> Union[sympy.Basic, float]:
return self._exponent

@property
def global_shift(self) -> float:
return self._global_shift

# virtual method
def _with_exponent(self: TSelf,
exponent: Union[sympy.Basic, float]) -> TSelf:
Expand Down Expand Up @@ -319,6 +323,9 @@ def _resolve_parameters_(self: TSelf, param_resolver) -> TSelf:
return self._with_exponent(
exponent=param_resolver.value_of(self._exponent))

def _json_dict_(self):
return protocols.to_json_dict(self, ['exponent', 'global_shift'])


def _lcm(vals: Iterable[int]) -> int:
t = 1
Expand Down
3 changes: 3 additions & 0 deletions cirq/ops/gate_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ def __str__(self):
return '{}({})'.format(self.gate,
', '.join(str(e) for e in self.qubits))

def _json_dict_(self):
return protocols.to_json_dict(self, ['gate', 'qubits'])

def _group_interchangeable_qubits(self) -> Tuple[
Union[raw_types.Qid,
Tuple[int, FrozenSet[raw_types.Qid]]],
Expand Down
3 changes: 3 additions & 0 deletions cirq/protocols/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
from cirq.protocols.inverse import (
inverse,
)
from cirq.protocols.json import (
to_json, read_json, to_json_dict
)
from cirq.protocols.measurement_key import (
is_measurement,
measurement_key,
Expand Down
88 changes: 88 additions & 0 deletions cirq/protocols/json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copyright 2019 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 string
import json
from typing import TYPE_CHECKING, Union, Any, Tuple, TypeVar, Optional, Dict, \
Iterable

import numpy as np

from typing_extensions import Protocol

from cirq.type_workarounds import NotImplementedType

TDefault = TypeVar('TDefault')

RaiseTypeErrorIfNotProvided = ([],) # type: Any


class SupportsJSON(Protocol):
"""An object that can be turned into JSON dictionaries.

Returning `NotImplemented` or `None` means "don't know how to turn into
QASM". In that case fallbacks based on decomposition and known unitaries
will be used instead.
"""

def _json_dict_(self) -> Union[None, NotImplementedType, Dict[Any, Any]]:
pass


def to_json_dict(obj, attribute_names):
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit worried that we're straying into territory that should be solved at the python level instead of at a library level. I don't have a better suggestion, there's just an alarm going off in my head 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.

@dataclasses can use the asdict helper. In general, I think hacking into the __dict__ is a little too magic

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Synced offline with @Strilanc. His objection was to the concept of serialization and that it should be done at the python level. Python's built-in serialization is pickle which is too brittle for our case; python's json module in the standard library is what we exploit here and follow the general pattern for extending that library

d = {'cirq_type': obj.__class__.__name__}
for attr_name in attribute_names:
d[attr_name] = getattr(obj, attr_name)
return d


class CirqEncoder(json.JSONEncoder):
def default(self, o):
if hasattr(o, '_json_dict_'):
return o._json_dict_()
if isinstance(o, np.ndarray):
return o.tolist()
if isinstance(o, np.int_):
return o.item()
return super().default(o)


def cirq_object_hook(d):
if 'cirq_type' in d:
import cirq
cls = getattr(cirq, d['cirq_type'])

if hasattr(cls, '_from_json_dict_'):
return cls._from_json_dict_(**d)

del d['cirq_type']
return cls(**d)

return d


def to_json(obj: Any, file, *, indent=2, cls=CirqEncoder):
if isinstance(file, str):
with open(file, 'w') as actually_a_file:
return json.dump(obj, actually_a_file, indent=indent, cls=cls)

return json.dump(obj, file, indent=indent, cls=cls)


def read_json(file_or_fn, object_hook=cirq_object_hook):
if isinstance(file_or_fn, str):
with open(file_or_fn, 'r') as file:
return json.load(file, object_hook=object_hook)

return json.load(file_or_fn, object_hook=object_hook)
99 changes: 99 additions & 0 deletions cirq/protocols/json_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Copyright 2019 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 pytest

import cirq
import cirq.protocols
import io


def assert_roundtrip(obj, text_should_be=None):
buffer = io.StringIO()
cirq.protocols.to_json(obj, buffer)

if text_should_be is not None:
buffer.seek(0)
text = buffer.read()

print()
print(text)

assert text == text_should_be

buffer.seek(0)
obj2 = cirq.protocols.read_json(buffer)
assert obj == obj2


def test_line_qubit_roundtrip():
q1 = cirq.LineQubit(12)

buffer = io.StringIO()
cirq.protocols.to_json(q1, buffer)

buffer.seek(0)
text = buffer.read()

print()
print(text)

assert text == """{
"cirq_type": "LineQubit",
"x": 12
}"""

buffer.seek(0)
q2 = cirq.protocols.read_json(buffer)
assert q1 == q2


def test_op_roundtrip():
q = cirq.LineQubit(5)
op1 = cirq.Rx(.123).on(q)

buffer = io.StringIO()
cirq.protocols.to_json(op1, buffer)

buffer.seek(0)
text = buffer.read()
assert text == """{
"cirq_type": "GateOperation",
"gate": {
"cirq_type": "XPowGate",
"exponent": 0.03915211600060625,
"global_shift": -0.5
},
"qubits": [
{
"cirq_type": "LineQubit",
"x": 5
}
]
}"""

print()
print(text)

buffer.seek(0)
op2 = cirq.protocols.read_json(buffer)
assert op1 == op2


def test_gridqubit_roundtrip():
q = cirq.GridQubit(15, 18)
assert_roundtrip(q, text_should_be="""{
"cirq_type": "GridQubit",
"row": 15,
"col": 18
}""")