-
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
Lock down CircuitOperation and ParamResolver #5548
Lock down CircuitOperation and ParamResolver #5548
Conversation
Changes to resolve type conflicts were made with the goal of minimizing impact to the code. This may result in slightly reduced performance in the affected code (specifically any of the new If we really wanted to, we could retype the affected protocols to use |
@@ -531,7 +531,7 @@ def measure_grouped_settings( | |||
for max_setting, param_resolver in itertools.product( | |||
grouped_settings.keys(), study.to_resolvers(circuit_sweep) | |||
): | |||
circuit_params = param_resolver.param_dict | |||
circuit_params = dict(param_resolver.param_dict) |
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.
Let's file an issue about fixing the types so we can avoid the copies here and in sample_expectation_values
below. I think these should be fixable without worrying too much about compatibility, since AFAICT the dicts are not exposed anywhere in a way that is intended to be mutable, but we can deal with that in a future PR.
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.
Opened #5554.
frozenset(self.measurement_key_map.items()), | ||
self.param_resolver, | ||
self.parent_path, | ||
tuple([] if self.repetition_ids is None else self.repetition_ids), |
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.
alternately:
tuple([] if self.repetition_ids is None else self.repetition_ids), | |
() if self.repetition_ids is None else tuple(self.repetition_ids), |
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.
Done.
@@ -259,7 +315,7 @@ def _measurement_key_objs_(self) -> AbstractSet['cirq.MeasurementKey']: | |||
self, | |||
'_cached_measurement_key_objs', | |||
{ | |||
protocols.with_measurement_key_mapping(key, self.measurement_key_map) | |||
protocols.with_measurement_key_mapping(key, dict(self.measurement_key_map)) |
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.
Let's file an issue to fix the with_measurement_key_mapping
protocol to we can avoid the copy here and on line 361 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.
Done: #5552. Unclear if this should be before or after 1.0 (may be more effort than it's worth likely breaking but not 100% certain), so I marked as triage-discuss.
kwargs = { | ||
'circuit': self.circuit, | ||
'repetitions': self.repetitions, | ||
'qubit_map': self.qubit_map, | ||
'measurement_key_map': self.measurement_key_map, | ||
'param_resolver': self.param_resolver, | ||
'repetition_ids': self.repetition_ids, | ||
'parent_path': self.parent_path, | ||
'extern_keys': self._extern_keys, | ||
'use_repetition_ids': self.use_repetition_ids, | ||
'repeat_until': self.repeat_until, | ||
} | ||
kwargs.update(changes) |
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 use literal unpacking since later value overwrite earlier ones:
kwargs = { | |
'circuit': self.circuit, | |
'repetitions': self.repetitions, | |
'qubit_map': self.qubit_map, | |
'measurement_key_map': self.measurement_key_map, | |
'param_resolver': self.param_resolver, | |
'repetition_ids': self.repetition_ids, | |
'parent_path': self.parent_path, | |
'extern_keys': self._extern_keys, | |
'use_repetition_ids': self.use_repetition_ids, | |
'repeat_until': self.repeat_until, | |
} | |
kwargs.update(changes) | |
kwargs = { | |
'circuit': self.circuit, | |
'repetitions': self.repetitions, | |
'qubit_map': self.qubit_map, | |
'measurement_key_map': self.measurement_key_map, | |
'param_resolver': self.param_resolver, | |
'repetition_ids': self.repetition_ids, | |
'parent_path': self.parent_path, | |
'extern_keys': self._extern_keys, | |
'use_repetition_ids': self.use_repetition_ids, | |
'repeat_until': self.repeat_until, | |
**changes, | |
} |
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.
Done.
# These fields are purely private: they should not be accessed | ||
# from outside this class. | ||
self._hash = None | ||
self._cached_measurement_key_objs = None | ||
self._cached_control_keys = None | ||
self._cached_mapped_single_loop = None | ||
self._extern_keys = extern_keys |
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 make these cached properties instead:
from cirq._compat import cached_property
...
@cached_property
def _hash(self) -> int:
return hash(...)
This should simplify some code below and let us get rid of a few more object.__setattr__
calls.
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.
Done. (The __setattr__
calls were no longer needed anyhow, but it's a good change anyways)
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.
Didn't know this existed! There are lots of other places we do manual caching for our immutable circuit and op classes and final results. (I looked into using functools.lru_cache for those, but that caused memory leaks)
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.
Yes, cached_property
is very useful (it's part of functools
in the standard library starting in python 3.8, but we still have to use a backport for now until we drop support for 3.7).
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.
Minor comments, then LGTM.
} | ||
|
||
def _measurement_key_objs_(self) -> AbstractSet['cirq.MeasurementKey']: | ||
return self._measurement_key_objs # type: ignore |
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.
What's the type error here?
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.
Looks like it was carry-over from the old code. Removed.
circuit = protocols.resolve_parameters( | ||
circuit, self.param_resolver, recursive=False | ||
) | ||
return cast(circuits.Circuit, circuit) |
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.
Do we have to return cirq.Circuit
here or could this be cirq.AbstractCircuit
? If the former then I'd suggest unfreezing instead:
return cast(circuits.Circuit, circuit) | |
return circuit.unfreeze(copy=False) |
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.
Public methods that consume this expect Circuit
, so I think we're stuck with that. unfreeze
seems to work well, though.
) | ||
), | ||
@cached_property | ||
def _hash(self): |
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.
nit: add return type
def _hash(self): | |
def _hash(self) -> int: |
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.
Done
) | ||
) | ||
|
||
def __hash__(self): |
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.
here, too, can add return type.
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.
and done.
extern_keys: FrozenSet['cirq.MeasurementKey'] = frozenset(), | ||
use_repetition_ids: bool = True, | ||
repeat_until: Optional['cirq.Condition'] = 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.
Should the docstring be moved from the class to here?
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.
Done.
if q_new.dimension != q.dimension: | ||
raise ValueError(f'Qid dimension conflict.\nFrom qid: {q}\nTo qid: {q_new}') | ||
|
||
self._measurement_key_map = measurement_key_map or {} |
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.
Defensive copy for the dicts? Or comment in the docstring to state no defensive copy will be made?
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 good call, as neither should be large enough for copy to be an issue and the dict must not change. Technically a breaking change, but that's par for the course on this PR.
Automerge cancelled: A required status check is not present. Missing statuses: ['Pytest Ubuntu (3.7)', 'Pytest Ubuntu (3.8)', 'Pytest Ubuntu (3.9)', 'Pytest Windows (3.7)', 'Pytest Windows (3.8)', 'Pytest Windows (3.9)', 'Typescript lint check', 'Typescript tests', 'Typescript tests coverage'] |
* Lock down CircuitOperation attributes. * Reduce attribute lockdown * Resolve type conflicts * review comments * docs and defensive copies * document error modes Co-authored-by: Cirq Bot <craiggidney+github+cirqbot@google.com>
Fixes quantumlib#5552. Since this change only* affects type annotations, it is non-breaking. Any breakages due to `ParamResolver.param_dict` becoming immutable should instead refer to quantumlib#5548, where this change was applied. *Okay, it also removes some `dict` calls, but those were added in quantumlib#5548.
* Lock down CircuitOperation attributes. * Reduce attribute lockdown * Resolve type conflicts * review comments * docs and defensive copies * document error modes Co-authored-by: Cirq Bot <craiggidney+github+cirqbot@google.com>
Fixes quantumlib#5552. Since this change only* affects type annotations, it is non-breaking. Any breakages due to `ParamResolver.param_dict` becoming immutable should instead refer to quantumlib#5548, where this change was applied. *Okay, it also removes some `dict` calls, but those were added in quantumlib#5548.
Fixes #4327. Fixes #5507.
This PR converts all
ParamResolver
andCircuitOperation
attributes to_private
attributes with read-only@property
accessors. TheCircuitOperation.extern_keys
field is not given an accessor as it is exclusively for decomposition and should never be referenced outside the class.Attributes with mutable types (e.g. dicts) are still technically possible to mutate, but will fail mypy checks if this is attempted. Comments have been added to further clarify the prohibition on mutation.
This is technically a breaking change, but the workflows it breaks are limited to mutations of
CircuitOperation
andParamResolver
which were advised against in docstrings.