-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Adding tags to Moments #7467
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?
Adding tags to Moments #7467
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,7 @@ | |
Any, | ||
Callable, | ||
cast, | ||
Hashable, | ||
Iterable, | ||
Iterator, | ||
Mapping, | ||
|
@@ -77,7 +78,12 @@ class Moment: | |
are no such operations, returns an empty Moment. | ||
""" | ||
|
||
def __init__(self, *contents: cirq.OP_TREE, _flatten_contents: bool = True) -> None: | ||
def __init__( | ||
self, | ||
*contents: cirq.OP_TREE, | ||
_flatten_contents: bool = True, | ||
tags: tuple[Hashable, ...] = (), | ||
) -> None: | ||
"""Constructs a moment with the given operations. | ||
|
||
Args: | ||
|
@@ -110,6 +116,7 @@ def __init__(self, *contents: cirq.OP_TREE, _flatten_contents: bool = True) -> N | |
|
||
self._measurement_key_objs: frozenset[cirq.MeasurementKey] | None = None | ||
self._control_keys: frozenset[cirq.MeasurementKey] | None = None | ||
self._tags = tags | ||
|
||
@classmethod | ||
def from_ops(cls, *ops: cirq.Operation) -> cirq.Moment: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let us add an optional |
||
|
@@ -133,6 +140,32 @@ def operations(self) -> tuple[cirq.Operation, ...]: | |
def qubits(self) -> frozenset[cirq.Qid]: | ||
return frozenset(self._qubit_to_op) | ||
|
||
@property | ||
def tags(self) -> tuple[Hashable, ...]: | ||
"""Returns a tuple of the operation's tags.""" | ||
return self._tags | ||
|
||
def with_tags(self, *new_tags: Hashable) -> cirq.Moment: | ||
"""Creates a new Moment with the current ops and the specified tags. | ||
|
||
If the moment already has tags, this will add the new_tags to the | ||
preexisting tags. | ||
|
||
This method can be used to attach meta-data to moments | ||
without affecting their functionality. The intended usage is to | ||
attach classes intended for this purpose or strings to mark operations | ||
for specific usage that will be recognized by consumers. | ||
|
||
Tags can be a list of any type of object that is useful to identify | ||
this operation as long as the type is hashable. If you wish the | ||
resulting operation to be eventually serialized into JSON, you should | ||
also restrict the operation to be JSON serializable. | ||
|
||
Please note that tags should be instantiated if classes are | ||
used. Raw types are not allowed. | ||
""" | ||
return Moment(*self._operations, _flatten_contents=False, tags=(*self._tags, *new_tags)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit - consider adding a shortcut |
||
|
||
def operates_on_single_qubit(self, qubit: cirq.Qid) -> bool: | ||
"""Determines if the moment has operations touching the given qubit. | ||
Args: | ||
|
@@ -183,7 +216,7 @@ def with_operation(self, operation: cirq.Operation) -> cirq.Moment: | |
raise ValueError(f'Overlapping operations: {operation}') | ||
|
||
# Use private variables to facilitate a quick copy. | ||
m = Moment(_flatten_contents=False) | ||
m = Moment(_flatten_contents=False, tags=self._tags) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PR description says tags are lost if the moment undergoes transformation, but here tags are retained. Either way is probably fine, but wanted to make sure this change was intentional. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 - perhaps it would be simpler to remove tags anytime we return a new Moment with changed operations. This should apply to the Currently there is an inconsistency that Or is there some requirement that tags should be sticky and survive some Moment transformations? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Depends on definition of "simpler". If Moment was a dataclass, which I think there's a decent argument that it should be, it'd be simpler to leave them. That said, I think it's understandable if the So, I don't have much of a preference whether the |
||
m._operations = self._operations + (operation,) | ||
m._sorted_operations = None | ||
m._qubit_to_op = {**self._qubit_to_op, **{q: operation for q in operation.qubits}} | ||
|
@@ -212,7 +245,7 @@ def with_operations(self, *contents: cirq.OP_TREE) -> cirq.Moment: | |
if not flattened_contents: | ||
return self | ||
|
||
m = Moment(_flatten_contents=False) | ||
m = Moment(_flatten_contents=False, tags=self._tags) | ||
# Use private variables to facilitate a quick copy. | ||
m._qubit_to_op = self._qubit_to_op.copy() | ||
for op in flattened_contents: | ||
|
@@ -510,18 +543,26 @@ def _superoperator_(self) -> np.ndarray: | |
return qis.kraus_to_superoperator(self._kraus_()) | ||
|
||
def _json_dict_(self) -> dict[str, Any]: | ||
return protocols.obj_to_dict_helper(self, ['operations']) | ||
# For backwards compatibility, only output tags if they exist. | ||
args = ['operations', 'tags'] if self._tags else ['operations'] | ||
return protocols.obj_to_dict_helper(self, args) | ||
|
||
@classmethod | ||
def _from_json_dict_(cls, operations, **kwargs): | ||
return cls.from_ops(*operations) | ||
def _from_json_dict_(cls, operations, tags=(), **kwargs): | ||
return cls(*operations, tags=tags) | ||
|
||
def __add__(self, other: cirq.OP_TREE) -> cirq.Moment: | ||
if isinstance(other, circuit.AbstractCircuit): | ||
return NotImplemented # Delegate to Circuit.__radd__. | ||
if isinstance(other, Moment): | ||
return self.with_tags(*other.tags).with_operations(other) | ||
return self.with_operations(other) | ||
|
||
def __sub__(self, other: cirq.OP_TREE) -> cirq.Moment: | ||
if isinstance(other, Moment): | ||
new_tags = tuple(tag for tag in self._tags if tag not in other.tags) | ||
else: | ||
new_tags = self._tags | ||
must_remove = set(op_tree.flatten_to_ops(other)) | ||
new_ops = [] | ||
for op in self.operations: | ||
|
@@ -535,7 +576,7 @@ def __sub__(self, other: cirq.OP_TREE) -> cirq.Moment: | |
f"Missing operations: {must_remove!r}\n" | ||
f"Moment: {self!r}" | ||
) | ||
return Moment(new_ops) | ||
return Moment(new_ops, tags=new_tags) | ||
|
||
@overload | ||
def __getitem__(self, key: raw_types.Qid) -> cirq.Operation: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,9 @@ | |
cirq.X(cirq.LineQubit(0)), | ||
cirq.Y(cirq.LineQubit(1)), | ||
cirq.Z(cirq.LineQubit(2)), | ||
)] | ||
), | ||
cirq.Moment( | ||
cirq.X(cirq.LineQubit(0)), | ||
tags=cirq.Duration(nanos=25) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does not fit the declared |
||
) | ||
] |
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 we add a docstring for tags or is it intentionally hidden?