-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add JSON serialisations #7396
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
base: main
Are you sure you want to change the base?
Add JSON serialisations #7396
Conversation
@WingCode - thank you for working on this. The failing CI checks should give some pointers on what is amiss. Feel free to ask here if you need clarification. |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #7396 +/- ##
========================================
Coverage 98.69% 98.69%
========================================
Files 1112 1114 +2
Lines 97937 98097 +160
========================================
+ Hits 96659 96819 +160
Misses 1278 1278 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
@pavoljuhas I have fixed the CI checks. |
@@ -128,7 +178,18 @@ def noisy_moment(self, moment: cirq.Moment, system_qubits: Sequence[cirq.Qid]): | |||
return output if self._prepend else output[::-1] | |||
return moment | |||
|
|||
def _json_dict_(self) -> dict[str, object]: | |||
return {'readout_decay_gate': self.readout_decay_gate, 'prepend': self._prepend} |
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.
Can we instead serialize the decay probability instead of the gate, similar to above? The gate is somewhat of an internal detail.
@@ -54,7 +65,18 @@ def noisy_moment(self, moment: cirq.Moment, system_qubits: Sequence[cirq.Qid]): | |||
] | |||
return output[::-1] if self._prepend else output | |||
|
|||
def _json_dict_(self) -> dict[str, object]: | |||
return {'qubit_noise_gate': self.qubit_noise_gate, 'prepend': self._prepend} |
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.
I think we should serialize the probability rather than the gate.
(Ok to add that as a member variable if it makes it easier.
Same with other gates below.
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.
The code for serialization of contrib classes should be moved under cirq.contrib to limit any dependency of non-contrib cirq on cirq.contrib. Please see inline comments for other suggestions.
# Avoid importing the optional contrib module at import time which would | ||
# require optional dependencies. Instead, import noise models lazily | ||
# when the resolver dictionary is created. |
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.
The classes below should be moved to contrib_class_resolver. They can use standard imports there without triggering the CI check that prohibits use of cirq.contrib from non-contrib cirq.
Also, the cirq.contrib module should register its resolver on import in a similar way as cirq_google below:
Cirq/cirq-google/cirq_google/__init__.py
Lines 114 to 118 in 6bb6661
# Register cirq_google's public classes for JSON serialization. | |
from cirq.protocols.json_serialization import _register_resolver | |
from cirq_google.json_resolver_cache import _class_resolver_dictionary | |
_register_resolver(_class_resolver_dictionary) |
@@ -0,0 +1,8 @@ | |||
{ |
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.
For the contrib classes we should add cirq-core/cirq/contrib/json_test_data/
directory in a similar fashion as for cirq-google/cirq_google/json_test_data. The json
and repr
files for contrib classes should be located there.
Also the TESTED_MODULES dictionary below should be updated with 'cirq.contrib'
entry so that the test code would look for files in .../contrib/json_test_data
.
(the key can be entered as f'cirq{"."}contrib'
to skip the check for cirq.contrib
)
Cirq/cirq-core/cirq/protocols/json_serialization_test.py
Lines 50 to 58 in 6bb6661
TESTED_MODULES: dict[str, _ModuleDeprecation | None] = { | |
'cirq_aqt': None, | |
'cirq_ionq': None, | |
'cirq_google': None, | |
'cirq_pasqal': None, | |
'cirq_rigetti': None, | |
'cirq.protocols': None, | |
'non_existent_should_be_fine': None, | |
} |
'Simulator', | ||
'StabilizerSampler', | ||
'DEFAULT_RESOLVERS', | ||
'NoiseModelFromNoiseProperties', |
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.
The 'NoiseModelFromNoiseProperties'
should be removed from the should_not_be_serialized
list if this PR can support serialization of NoiseModelFromNoiseProperties
. If that is too complicated, it is OK to limit the scope here to other NoiseModel classes only and leave NoiseModelFromNoiseProperties for later.
tuple(sorted((q, p) for q, p in (self.depol_probs or {}).items())), | ||
tuple(sorted((q, p) for q, p in (self.bitflip_probs or {}).items())), | ||
tuple(sorted((q, p) for q, p in (self.decay_probs or {}).items())), |
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.
Those dictionaries have simple float values. Would it work with just using them as are?
obj = cls.__new__(cls) | ||
obj.depol_probs = dict(depol_probs) | ||
obj.bitflip_probs = dict(bitflip_probs) | ||
obj.decay_probs = dict(decay_probs) |
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.
Please replace cls.__new__
with standard object construction cls(...)
here and in other files. If _json_dict_
above returns all __init__
arguments, this method should be unnecessary and removed.
return { | ||
'depol_probs': tuple((q, p) for q, p in (self.depol_probs or {}).items()), | ||
'bitflip_probs': tuple((q, p) for q, p in (self.bitflip_probs or {}).items()), | ||
'decay_probs': tuple((q, p) for q, p in (self.decay_probs or {}).items()), |
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.
will it work with returning the dictionaries as they are?
Also, consider using obj_to_dict_helper here.
def test_per_qubit_noise_model_repr_and_json(): | ||
q0 = cirq.GridQubit(0, 0) | ||
model = cirq_google.experimental.noise_models.PerQubitDepolarizingWithDampedReadoutNoiseModel( | ||
depol_probs={q0: 0.1}, bitflip_probs={q0: 0.2}, decay_probs={q0: 0.3} | ||
) | ||
cirq.testing.assert_equivalent_repr(model, setup_code='import cirq\nimport cirq_google') | ||
restored = type(model)._from_json_dict_(**model._json_dict_()) | ||
assert restored == model |
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 test should be unnecessary after adding PerQubitDepolarizingWithDampedReadoutNoiseModel.json
and PerQubitDepolarizingWithDampedReadoutNoiseModel.repr
to cirq_google/json_test_data.
cirq-core/cirq/contrib/__init__.py
Outdated
@@ -24,3 +24,4 @@ | |||
from cirq.contrib.qcircuit import circuit_to_latex_using_qcircuit as circuit_to_latex_using_qcircuit | |||
from cirq.contrib import json # noqa: F401 | |||
from cirq.contrib.circuitdag import CircuitDag as CircuitDag, Unique as Unique | |||
from cirq.contrib import noise_models as noise_models |
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.
We should try to avoid adding new import here. If cirq.contrib registers its class resolver here, the import of noise_models can be deferred to the resolver execution.
"cirq.devices.ThermalNoiseModel(" | ||
f"qubits={set(self.rate_matrix_GHz.keys())!r}, " | ||
f"gate_durations_ns={self.gate_durations_ns!r}, " | ||
f"heat_rate_GHz=None, cool_rate_GHz=None, dephase_rate_GHz=None, " |
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 drops the attributes' values, please fix.
@@ -342,3 +342,19 @@ def test_noisy_moment_two_qubit(): | |||
[9.87330937e-01, 0, 0, 9.95013725e-01], | |||
], | |||
) | |||
|
|||
|
|||
def test_thermal_noise_model_repr_and_json(): |
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 test (and other similar ones) should be replaced by adding ThermalNoiseModel.json
and ThermalNoiseModel.repr
files to json_test_data
. Please check instructions in https://github.com/quantumlib/Cirq/blob/main/docs/dev/serialization.md.
FYI that @pavoljuhas is out on jury duty (a mandatory civic obligation in the USA) starting today and possibly more days (depending on unpredictable factors). If he had commented on the PR or this issue before and hasn't responded, that's why. |
Please consider setting up a local python environment for development per https://github.com/quantumlib/Cirq/blob/main/docs/dev/development.md#setting-up-an-environment and testing the code on your side first. A full test suite takes a while to run, but you can get fast results using either
You can also consult the CI logs to find what tests are failing and specifically running those. |
Thank you for the helpful tip, and apologies for the oversight. I just realized that I was mistakenly pushing fixes to the wrong branch, which ended up triggering the CI unnecessarily. I’ll make sure to be more careful going forward. |
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.
I have not got through all files, but I think we can address the new comments before next iteration.
@@ -0,0 +1,35 @@ | |||
# pylint: disable=wrong-or-nonexistent-copyright-notice |
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.
Please replace with the actual copyright statement as in __init__.py
.
"QuantumVolumeResult", | ||
"SwapPermutationGate", | ||
"BayesianNetworkGate", |
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.
The first 3 classes are serializable and should NOT be listed here.
Please add the corresponding .repr
and .json
files instead.
After that you can replace the dictionary filtering below with
resolver_cache=_class_resolver_dictionary(),
@classmethod | ||
def _from_json_dict_(cls, depol_prob, prepend, **kwargs): | ||
return cls(depol_prob, prepend=prepend) |
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.
When _json_dict_
provides all __init__
arguments, the _from_json_dict_
method is redundant. Please remove here and in other similar instances in this PR.
@classmethod | |
def _from_json_dict_(cls, depol_prob, prepend, **kwargs): | |
return cls(depol_prob, prepend=prepend) |
self.require_physical_tag: bool = require_physical_tag | ||
self.skip_measurements: bool = skip_measurements | ||
self._prepend = prepend | ||
|
||
def _value_equality_values_(self): | ||
gate_times = tuple(sorted((k, v) for k, v in self.gate_durations_ns.items())) |
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.
gate_durations_ns
can be used as is, no need for conversion here.
gate_times = {cirq.json_cirq_type(k): v for k, v in self.gate_durations_ns.items()} | ||
return { | ||
'gate_durations_ns': tuple(gate_times.items()), | ||
'rate_matrix_GHz': tuple((q, m.tolist()) for q, m in self.rate_matrix_GHz.items()), |
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.
rate_matrix_GHz
has derived values which should not be stored. Please use it only to recover the qubits
for class init, which do not seem to be stored anywhere else. Otherwise, just store here only the init arguments with a minimum conversions needed.
def _json_dict_(self) -> dict[str, object]: | ||
gate_times = {cirq.json_cirq_type(k): v for k, v in self.gate_durations_ns.items()} | ||
return { | ||
'gate_durations_ns': tuple(gate_times.items()), |
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.
No need to convert to tuple, please store as a dictionary (which are native to json).
prepend, | ||
**kwargs, | ||
): | ||
obj = cls.__new__(cls) |
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.
Please replace with standard cls(args)
call. Introduction of cls.__new__
should have a very strong justification, otherwise it is often a sign the code needs another look.
json_text = cirq.to_json(model) | ||
assert cirq.read_json(json_text=json_text) == model |
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 duplicates implicit testing that happens with addition of ThermalNoiseModel.json and ThermalNoiseModel.repr files. Please delete here and also for other instances where .json
and .repr
files exist.
Co-authored-by: Pavol Juhas <[email protected]>
Co-authored-by: Pavol Juhas <[email protected]>
closes #3846