2424 Any ,
2525 Callable ,
2626 cast ,
27+ Hashable ,
2728 Iterable ,
2829 Iterator ,
2930 Mapping ,
@@ -77,7 +78,12 @@ class Moment:
7778 are no such operations, returns an empty Moment.
7879 """
7980
80- def __init__ (self , * contents : cirq .OP_TREE , _flatten_contents : bool = True ) -> None :
81+ def __init__ (
82+ self ,
83+ * contents : cirq .OP_TREE ,
84+ _flatten_contents : bool = True ,
85+ tags : tuple [Hashable , ...] = (),
86+ ) -> None :
8187 """Constructs a moment with the given operations.
8288
8389 Args:
@@ -88,6 +94,12 @@ def __init__(self, *contents: cirq.OP_TREE, _flatten_contents: bool = True) -> N
8894 we skip flattening and assume that contents already consists
8995 of individual operations. This is used internally by helper
9096 methods to avoid unnecessary validation.
97+ tags: Optional tags to denote specific Moment objects with meta-data.
98+ These are a tuple of any Hashable object. Typically, a class
99+ will be passed. Tags apply only to this specific set of operations
100+ and will be lost on any transformation of the
101+ Moment. For instance, if operations are added to the Moment, tags
102+ will be dropped unless explicitly added back in by the user.
91103
92104 Raises:
93105 ValueError: A qubit appears more than once.
@@ -110,9 +122,10 @@ def __init__(self, *contents: cirq.OP_TREE, _flatten_contents: bool = True) -> N
110122
111123 self ._measurement_key_objs : frozenset [cirq .MeasurementKey ] | None = None
112124 self ._control_keys : frozenset [cirq .MeasurementKey ] | None = None
125+ self ._tags = tags
113126
114127 @classmethod
115- def from_ops (cls , * ops : cirq .Operation ) -> cirq .Moment :
128+ def from_ops (cls , * ops : cirq .Operation , tags : tuple [ Hashable , ...] = () ) -> cirq .Moment :
116129 """Construct a Moment from the given operations.
117130
118131 This avoids calling `flatten_to_ops` in the moment constructor, which
@@ -122,8 +135,11 @@ def from_ops(cls, *ops: cirq.Operation) -> cirq.Moment:
122135
123136 Args:
124137 *ops: Operations to include in the Moment.
138+ tags: Optional tags to denote specific Moment objects with meta-data.
139+ These are a tuple of any Hashable object. Tags will be dropped if
140+ the operations in the Moment are modified or transformed.
125141 """
126- return cls (* ops , _flatten_contents = False )
142+ return cls (* ops , _flatten_contents = False , tags = tags )
127143
128144 @property
129145 def operations (self ) -> tuple [cirq .Operation , ...]:
@@ -133,6 +149,34 @@ def operations(self) -> tuple[cirq.Operation, ...]:
133149 def qubits (self ) -> frozenset [cirq .Qid ]:
134150 return frozenset (self ._qubit_to_op )
135151
152+ @property
153+ def tags (self ) -> tuple [Hashable , ...]:
154+ """Returns a tuple of the operation's tags."""
155+ return self ._tags
156+
157+ def with_tags (self , * new_tags : Hashable ) -> cirq .Moment :
158+ """Creates a new Moment with the current ops and the specified tags.
159+
160+ If the moment already has tags, this will add the new_tags to the
161+ preexisting tags.
162+
163+ This method can be used to attach meta-data to moments
164+ without affecting their functionality. The intended usage is to
165+ attach classes intended for this purpose or strings to mark operations
166+ for specific usage that will be recognized by consumers.
167+
168+ Tags can be a list of any type of object that is useful to identify
169+ this operation as long as the type is hashable. If you wish the
170+ resulting operation to be eventually serialized into JSON, you should
171+ also restrict the operation to be JSON serializable.
172+
173+ Please note that tags should be instantiated if classes are
174+ used. Raw types are not allowed.
175+ """
176+ if not new_tags :
177+ return self
178+ return Moment (* self ._operations , _flatten_contents = False , tags = (* self ._tags , * new_tags ))
179+
136180 def operates_on_single_qubit (self , qubit : cirq .Qid ) -> bool :
137181 """Determines if the moment has operations touching the given qubit.
138182 Args:
@@ -170,6 +214,8 @@ def operation_at(self, qubit: raw_types.Qid) -> cirq.Operation | None:
170214 def with_operation (self , operation : cirq .Operation ) -> cirq .Moment :
171215 """Returns an equal moment, but with the given op added.
172216
217+ Any tags on the Moment will be dropped.
218+
173219 Args:
174220 operation: The operation to append.
175221
@@ -198,6 +244,9 @@ def with_operation(self, operation: cirq.Operation) -> cirq.Moment:
198244 def with_operations (self , * contents : cirq .OP_TREE ) -> cirq .Moment :
199245 """Returns a new moment with the given contents added.
200246
247+ Any tags on the original Moment object are dropped if the Moment
248+ is changed.
249+
201250 Args:
202251 *contents: New operations to add to this moment.
203252
@@ -235,6 +284,9 @@ def with_operations(self, *contents: cirq.OP_TREE) -> cirq.Moment:
235284 def without_operations_touching (self , qubits : Iterable [cirq .Qid ]) -> cirq .Moment :
236285 """Returns an equal moment, but without ops on the given qubits.
237286
287+ Any tags on the original Moment object are dropped if the Moment
288+ is changed.
289+
238290 Args:
239291 qubits: Operations that touch these will be removed.
240292
@@ -510,11 +562,13 @@ def _superoperator_(self) -> np.ndarray:
510562 return qis .kraus_to_superoperator (self ._kraus_ ())
511563
512564 def _json_dict_ (self ) -> dict [str , Any ]:
513- return protocols .obj_to_dict_helper (self , ['operations' ])
565+ # For backwards compatibility, only output tags if they exist.
566+ args = ['operations' , 'tags' ] if self ._tags else ['operations' ]
567+ return protocols .obj_to_dict_helper (self , args )
514568
515569 @classmethod
516- def _from_json_dict_ (cls , operations , ** kwargs ):
517- return cls . from_ops (* operations )
570+ def _from_json_dict_ (cls , operations , tags = (), ** kwargs ):
571+ return cls (* operations , tags = tags )
518572
519573 def __add__ (self , other : cirq .OP_TREE ) -> cirq .Moment :
520574 if isinstance (other , circuit .AbstractCircuit ):
0 commit comments