-
Notifications
You must be signed in to change notification settings - Fork 982
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
Optimize is_measurement
protocol
#4140
Conversation
assert c_op._is_measurement_() is NotImplemented | ||
# Memoize `has_measurements` in the circuit. | ||
assert circuit.has_measurements() | ||
assert c_op._is_measurement_() is True |
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 red flag to me - memoization should not change the public properties of an object.
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.
True, but _is_measurement_
isn't really a public property. cirq.is_measurement
still gives the same answer (line 88) before memoization.
_is_measurement_
is just the first (assumed to be efficient) strategy in calculating the protocol result. It should be allowed to return NotImplemented
and let the protocol fallback to alternate strategies (i.e. decomposition).
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.
See my other comment - not memoizing in _is_measurement_
prevents it from becoming efficient unless the user calls has_measurement
, which is inconvenient behavior.
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.
Makes sense. See my other reply for the caveat in this approach.
@@ -136,6 +136,11 @@ def are_all_measurements_terminal(self) -> bool: | |||
|
|||
# End of memoized methods. | |||
|
|||
def _is_measurement_(self) -> bool: | |||
if self._has_measurements is None: | |||
return NotImplemented |
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.
As noted above - if _has_measurements
is not set, this should set it.
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.
Calculating has_measurement
from scratch can be very costly. _is_measurement_
cannot be expected to do the full circuit decomposition inside it, the protocol should handle it in the case where _is_measurement_
does not have an efficient answer.
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 why I'm concerned: for fc = FrozenCircuit(...)
if someone invokes cirq.is_measurement(fc)
, then later invokes fc.has_measurement()
, the result will not be memoized for the has_measurement
call.
Ideally, something like this would work:
def _is_measurement_(self) -> bool:
if self._has_measurements is None:
self._has_measurements = cirq.is_measurement(self.unfreeze())
return self._has_measurements
Deferring is_measurement
handling to the Circuit
class prevents infinite recursion in the _is_measurement_from_magic_method
step of the protocol.
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.
Updated to enable memoization. This comes with the downside that a CircuitOperation
, once encountered, will now be immediately fully evaluated regardless of how much decomposing it needs. I was aesthetically hesitant to go this route since it deviates from a pure breadth-first search for a measurement. But memoizing the expensive is_measurement
call is definitely a net win and worth it.
The is_measurement
protocol might slow down a bit for circuits with many non-measurement terminal CircuitOperations
, but these will probably be rare.
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.
Loss of BFS is unfortunate...I have a nagging feeling that there's a clean solution that allows BFS to coexist with proper memoization, but the existing tangle of methods and expected behavior make implementing that solution infeasible :/
@@ -136,6 +136,11 @@ def are_all_measurements_terminal(self) -> bool: | |||
|
|||
# End of memoized methods. | |||
|
|||
def _is_measurement_(self) -> bool: | |||
if self._has_measurements is None: | |||
return NotImplemented |
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 why I'm concerned: for fc = FrozenCircuit(...)
if someone invokes cirq.is_measurement(fc)
, then later invokes fc.has_measurement()
, the result will not be memoized for the has_measurement
call.
Ideally, something like this would work:
def _is_measurement_(self) -> bool:
if self._has_measurements is None:
self._has_measurements = cirq.is_measurement(self.unfreeze())
return self._has_measurements
Deferring is_measurement
handling to the Circuit
class prevents infinite recursion in the _is_measurement_from_magic_method
step of the protocol.
assert c_op._is_measurement_() is NotImplemented | ||
# Memoize `has_measurements` in the circuit. | ||
assert circuit.has_measurements() | ||
assert c_op._is_measurement_() is True |
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.
See my other comment - not memoizing in _is_measurement_
prevents it from becoming efficient unless the user calls has_measurement
, which is inconvenient behavior.
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.
Approval granted, but we should either leave #4141 open or create a follow-up issue to disentangle BFS from memoization.
Automerge cancelled: Only collaborators with write permission can use automerge. |
* Optimize is_measurement protocol * Optimize is_measurement protocol * No shortcut in circuit * Change from recursive to iterative * add test and remove vals_to_decompose arg * FrozenCircuit uses evaluates fully for memoization * Coverage for tagged non-GateOperation
Current
is_measurement
implementation is slow because:_measurement_key_
or_measurement_keys_
implemented.Made a few optimizations to move this faster:
_is_measurement_
magic methods on some of the objects for slightly faster evaluationFalse
, enabling us to skip it entirely regardless of whether it is decomposable or not._is_measurement_
checks before attempting anydecompose
This implementation may perform worse than the existing one on circuits that have many many levels of nested
CircuitOperations
or other composite gates at the start of the circuit and only these ones have measurements. This seems like a pretty non-typical case.