@@ -188,28 +188,20 @@ def _from_moments(cls: Type[CIRCUIT_TYPE], moments: Iterable['cirq.Moment']) ->
188
188
def moments (self ) -> Sequence ['cirq.Moment' ]:
189
189
pass
190
190
191
+ @abc .abstractmethod
191
192
def freeze (self ) -> 'cirq.FrozenCircuit' :
192
193
"""Creates a FrozenCircuit from this circuit.
193
194
194
195
If 'self' is a FrozenCircuit, the original object is returned.
195
196
"""
196
- from cirq .circuits import FrozenCircuit
197
-
198
- if isinstance (self , FrozenCircuit ):
199
- return self
200
-
201
- return FrozenCircuit (self , strategy = InsertStrategy .EARLIEST )
202
197
198
+ @abc .abstractmethod
203
199
def unfreeze (self , copy : bool = True ) -> 'cirq.Circuit' :
204
200
"""Creates a Circuit from this circuit.
205
201
206
202
Args:
207
203
copy: If True and 'self' is a Circuit, returns a copy that circuit.
208
204
"""
209
- if isinstance (self , Circuit ):
210
- return Circuit .copy (self ) if copy else self
211
-
212
- return Circuit (self , strategy = InsertStrategy .EARLIEST )
213
205
214
206
def __bool__ (self ):
215
207
return bool (self .moments )
@@ -822,6 +814,9 @@ def has_measurements(self):
822
814
"""
823
815
return protocols .is_measurement (self )
824
816
817
+ def _is_measurement_ (self ) -> bool :
818
+ return any (protocols .is_measurement (op ) for op in self .all_operations ())
819
+
825
820
def are_all_measurements_terminal (self ) -> bool :
826
821
"""Whether all measurement gates are at the end of the circuit.
827
822
@@ -1383,8 +1378,7 @@ def save_qasm(
1383
1378
self ._to_qasm_output (header , precision , qubit_order ).save (file_path )
1384
1379
1385
1380
def _json_dict_ (self ):
1386
- ret = protocols .obj_to_dict_helper (self , ['moments' ])
1387
- return ret
1381
+ return protocols .obj_to_dict_helper (self , ['moments' ])
1388
1382
1389
1383
@classmethod
1390
1384
def _from_json_dict_ (cls , moments , ** kwargs ):
@@ -1759,6 +1753,16 @@ def __init__(
1759
1753
circuit.
1760
1754
"""
1761
1755
self ._moments : List ['cirq.Moment' ] = []
1756
+
1757
+ # Implementation note: the following cached properties are set lazily and then
1758
+ # invalidated and reset to None in `self._mutated()`, which is called any time
1759
+ # `self._moments` is changed.
1760
+ self ._all_qubits : Optional [FrozenSet ['cirq.Qid' ]] = None
1761
+ self ._frozen : Optional ['cirq.FrozenCircuit' ] = None
1762
+ self ._is_measurement : Optional [bool ] = None
1763
+ self ._is_parameterized : Optional [bool ] = None
1764
+ self ._parameter_names : Optional [AbstractSet [str ]] = None
1765
+
1762
1766
flattened_contents = tuple (ops .flatten_to_ops_or_moments (contents ))
1763
1767
if all (isinstance (c , Moment ) for c in flattened_contents ):
1764
1768
self ._moments [:] = cast (Iterable [Moment ], flattened_contents )
@@ -1769,6 +1773,14 @@ def __init__(
1769
1773
else :
1770
1774
self .append (flattened_contents , strategy = strategy )
1771
1775
1776
+ def _mutated (self ) -> None :
1777
+ """Clear cached properties in response to this circuit being mutated."""
1778
+ self ._all_qubits = None
1779
+ self ._frozen = None
1780
+ self ._is_measurement = None
1781
+ self ._is_parameterized = None
1782
+ self ._parameter_names = None
1783
+
1772
1784
@classmethod
1773
1785
def _from_moments (cls , moments : Iterable ['cirq.Moment' ]) -> 'Circuit' :
1774
1786
new_circuit = Circuit ()
@@ -1831,6 +1843,41 @@ def _load_contents_with_earliest_strategy(self, contents: 'cirq.OP_TREE'):
1831
1843
def __copy__ (self ) -> 'cirq.Circuit' :
1832
1844
return self .copy ()
1833
1845
1846
+ def freeze (self ) -> 'cirq.FrozenCircuit' :
1847
+ """Gets a frozen version of this circuit.
1848
+
1849
+ Repeated calls to `.freeze()` will return the same FrozenCircuit
1850
+ instance as long as this circuit is not mutated.
1851
+ """
1852
+ from cirq .circuits .frozen_circuit import FrozenCircuit
1853
+
1854
+ if self ._frozen is None :
1855
+ self ._frozen = FrozenCircuit .from_moments (* self ._moments )
1856
+ return self ._frozen
1857
+
1858
+ def unfreeze (self , copy : bool = True ) -> 'cirq.Circuit' :
1859
+ return self .copy () if copy else self
1860
+
1861
+ def all_qubits (self ) -> FrozenSet ['cirq.Qid' ]:
1862
+ if self ._all_qubits is None :
1863
+ self ._all_qubits = super ().all_qubits ()
1864
+ return self ._all_qubits
1865
+
1866
+ def _is_measurement_ (self ) -> bool :
1867
+ if self ._is_measurement is None :
1868
+ self ._is_measurement = super ()._is_measurement_ ()
1869
+ return self ._is_measurement
1870
+
1871
+ def _is_parameterized_ (self ) -> bool :
1872
+ if self ._is_parameterized is None :
1873
+ self ._is_parameterized = super ()._is_parameterized_ ()
1874
+ return self ._is_parameterized
1875
+
1876
+ def _parameter_names_ (self ) -> AbstractSet [str ]:
1877
+ if self ._parameter_names is None :
1878
+ self ._parameter_names = super ()._parameter_names_ ()
1879
+ return self ._parameter_names
1880
+
1834
1881
def copy (self ) -> 'Circuit' :
1835
1882
"""Return a copy of this circuit."""
1836
1883
copied_circuit = Circuit ()
@@ -1856,11 +1903,13 @@ def __setitem__(self, key, value):
1856
1903
raise TypeError ('Can only assign Moments into Circuits.' )
1857
1904
1858
1905
self ._moments [key ] = value
1906
+ self ._mutated ()
1859
1907
1860
1908
# pylint: enable=function-redefined
1861
1909
1862
1910
def __delitem__ (self , key : Union [int , slice ]):
1863
1911
del self ._moments [key ]
1912
+ self ._mutated ()
1864
1913
1865
1914
def __iadd__ (self , other ):
1866
1915
self .append (other )
@@ -1889,6 +1938,7 @@ def __imul__(self, repetitions: _INT_TYPE):
1889
1938
if not isinstance (repetitions , (int , np .integer )):
1890
1939
return NotImplemented
1891
1940
self ._moments *= int (repetitions )
1941
+ self ._mutated ()
1892
1942
return self
1893
1943
1894
1944
def __mul__ (self , repetitions : _INT_TYPE ):
@@ -2032,6 +2082,7 @@ def _pick_or_create_inserted_op_moment_index(
2032
2082
2033
2083
if strategy is InsertStrategy .NEW or strategy is InsertStrategy .NEW_THEN_INLINE :
2034
2084
self ._moments .insert (splitter_index , Moment ())
2085
+ self ._mutated ()
2035
2086
return splitter_index
2036
2087
2037
2088
if strategy is InsertStrategy .INLINE :
@@ -2099,6 +2150,7 @@ def insert(
2099
2150
k = max (k , p + 1 )
2100
2151
if strategy is InsertStrategy .NEW_THEN_INLINE :
2101
2152
strategy = InsertStrategy .INLINE
2153
+ self ._mutated ()
2102
2154
return k
2103
2155
2104
2156
def insert_into_range (self , operations : 'cirq.OP_TREE' , start : int , end : int ) -> int :
@@ -2135,6 +2187,7 @@ def insert_into_range(self, operations: 'cirq.OP_TREE', start: int, end: int) ->
2135
2187
2136
2188
self ._moments [i ] = self ._moments [i ].with_operation (op )
2137
2189
op_index += 1
2190
+ self ._mutated ()
2138
2191
2139
2192
if op_index >= len (flat_ops ):
2140
2193
return end
@@ -2180,6 +2233,7 @@ def _push_frontier(
2180
2233
if n_new_moments > 0 :
2181
2234
insert_index = min (late_frontier .values ())
2182
2235
self ._moments [insert_index :insert_index ] = [Moment ()] * n_new_moments
2236
+ self ._mutated ()
2183
2237
for q in update_qubits :
2184
2238
if early_frontier .get (q , 0 ) > insert_index :
2185
2239
early_frontier [q ] += n_new_moments
@@ -2206,13 +2260,12 @@ def _insert_operations(
2206
2260
if len (operations ) != len (insertion_indices ):
2207
2261
raise ValueError ('operations and insertion_indices must have the same length.' )
2208
2262
self ._moments += [Moment () for _ in range (1 + max (insertion_indices ) - len (self ))]
2263
+ self ._mutated ()
2209
2264
moment_to_ops : Dict [int , List ['cirq.Operation' ]] = defaultdict (list )
2210
2265
for op_index , moment_index in enumerate (insertion_indices ):
2211
2266
moment_to_ops [moment_index ].append (operations [op_index ])
2212
2267
for moment_index , new_ops in moment_to_ops .items ():
2213
- self ._moments [moment_index ] = Moment (
2214
- self ._moments [moment_index ].operations + tuple (new_ops )
2215
- )
2268
+ self ._moments [moment_index ] = self ._moments [moment_index ].with_operations (* new_ops )
2216
2269
2217
2270
def insert_at_frontier (
2218
2271
self ,
@@ -2274,6 +2327,7 @@ def batch_remove(self, removals: Iterable[Tuple[int, 'cirq.Operation']]) -> None
2274
2327
old_op for old_op in copy ._moments [i ].operations if op != old_op
2275
2328
)
2276
2329
self ._moments = copy ._moments
2330
+ self ._mutated ()
2277
2331
2278
2332
def batch_replace (
2279
2333
self , replacements : Iterable [Tuple [int , 'cirq.Operation' , 'cirq.Operation' ]]
@@ -2298,6 +2352,7 @@ def batch_replace(
2298
2352
old_op if old_op != op else new_op for old_op in copy ._moments [i ].operations
2299
2353
)
2300
2354
self ._moments = copy ._moments
2355
+ self ._mutated ()
2301
2356
2302
2357
def batch_insert_into (self , insert_intos : Iterable [Tuple [int , 'cirq.OP_TREE' ]]) -> None :
2303
2358
"""Inserts operations into empty spaces in existing moments.
@@ -2318,6 +2373,7 @@ def batch_insert_into(self, insert_intos: Iterable[Tuple[int, 'cirq.OP_TREE']])
2318
2373
for i , insertions in insert_intos :
2319
2374
copy ._moments [i ] = copy ._moments [i ].with_operations (insertions )
2320
2375
self ._moments = copy ._moments
2376
+ self ._mutated ()
2321
2377
2322
2378
def batch_insert (self , insertions : Iterable [Tuple [int , 'cirq.OP_TREE' ]]) -> None :
2323
2379
"""Applies a batched insert operation to the circuit.
@@ -2352,6 +2408,7 @@ def batch_insert(self, insertions: Iterable[Tuple[int, 'cirq.OP_TREE']]) -> None
2352
2408
if next_index > insert_index :
2353
2409
shift += next_index - insert_index
2354
2410
self ._moments = copy ._moments
2411
+ self ._mutated ()
2355
2412
2356
2413
def append (
2357
2414
self ,
@@ -2382,6 +2439,7 @@ def clear_operations_touching(
2382
2439
for k in moment_indices :
2383
2440
if 0 <= k < len (self ._moments ):
2384
2441
self ._moments [k ] = self ._moments [k ].without_operations_touching (qubits )
2442
+ self ._mutated ()
2385
2443
2386
2444
@property
2387
2445
def moments (self ) -> Sequence ['cirq.Moment' ]:
0 commit comments