cirq-core 1.2.0.dev20230717225858__py3-none-any.whl → 1.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. cirq/__init__.py +5 -0
  2. cirq/_compat.py +26 -11
  3. cirq/_compat_test.py +37 -3
  4. cirq/_version.py +31 -1
  5. cirq/_version_test.py +1 -1
  6. cirq/circuits/circuit.py +106 -32
  7. cirq/circuits/circuit_operation.py +2 -2
  8. cirq/circuits/circuit_operation_test.py +1 -1
  9. cirq/circuits/circuit_test.py +109 -3
  10. cirq/circuits/frozen_circuit.py +80 -5
  11. cirq/circuits/frozen_circuit_test.py +47 -2
  12. cirq/circuits/qasm_output_test.py +9 -9
  13. cirq/conftest.py +1 -2
  14. cirq/contrib/acquaintance/devices.py +1 -1
  15. cirq/contrib/hacks/disable_validation_test.py +1 -1
  16. cirq/contrib/noise_models/noise_models.py +1 -2
  17. cirq/contrib/paulistring/clifford_optimize.py +1 -1
  18. cirq/contrib/paulistring/clifford_target_gateset_test.py +4 -4
  19. cirq/contrib/qcircuit/qcircuit_pdf.py +1 -1
  20. cirq/contrib/quimb/density_matrix.py +2 -3
  21. cirq/contrib/quimb/grid_circuits.py +3 -3
  22. cirq/contrib/quimb/state_vector.py +3 -5
  23. cirq/contrib/routing/utils.py +1 -2
  24. cirq/contrib/svg/svg.py +4 -6
  25. cirq/devices/grid_qubit.py +49 -38
  26. cirq/devices/grid_qubit_test.py +1 -3
  27. cirq/devices/insertion_noise_model.py +21 -1
  28. cirq/devices/insertion_noise_model_test.py +6 -0
  29. cirq/devices/line_qubit.py +67 -40
  30. cirq/devices/named_topologies.py +8 -14
  31. cirq/devices/noise_properties.py +1 -1
  32. cirq/devices/noise_utils.py +7 -5
  33. cirq/devices/noise_utils_test.py +7 -0
  34. cirq/experiments/fidelity_estimation_test.py +1 -1
  35. cirq/experiments/qubit_characterizations.py +6 -5
  36. cirq/experiments/random_quantum_circuit_generation.py +1 -1
  37. cirq/experiments/random_quantum_circuit_generation_test.py +28 -1
  38. cirq/experiments/readout_confusion_matrix.py +6 -6
  39. cirq/experiments/xeb_fitting.py +3 -5
  40. cirq/experiments/xeb_fitting_test.py +2 -2
  41. cirq/experiments/xeb_sampling.py +1 -1
  42. cirq/interop/quirk/url_to_circuit.py +40 -38
  43. cirq/json_resolver_cache.py +2 -0
  44. cirq/linalg/decompositions.py +6 -5
  45. cirq/ops/__init__.py +2 -0
  46. cirq/ops/classically_controlled_operation.py +1 -1
  47. cirq/ops/clifford_gate.py +9 -9
  48. cirq/ops/clifford_gate_test.py +3 -4
  49. cirq/ops/common_channels.py +2 -5
  50. cirq/ops/common_channels_test.py +3 -5
  51. cirq/ops/common_gates_test.py +7 -7
  52. cirq/ops/controlled_operation_test.py +2 -2
  53. cirq/ops/dense_pauli_string.py +3 -0
  54. cirq/ops/eigen_gate_test.py +1 -3
  55. cirq/ops/fourier_transform.py +1 -2
  56. cirq/ops/fsim_gate.py +1 -1
  57. cirq/ops/gate_features_test.py +2 -2
  58. cirq/ops/gate_operation_test.py +1 -2
  59. cirq/ops/greedy_qubit_manager.py +86 -0
  60. cirq/ops/greedy_qubit_manager_test.py +98 -0
  61. cirq/ops/linear_combinations.py +1 -1
  62. cirq/ops/named_qubit.py +55 -18
  63. cirq/ops/parity_gates.py +65 -18
  64. cirq/ops/parity_gates_test.py +41 -2
  65. cirq/ops/pauli_gates.py +2 -2
  66. cirq/ops/pauli_string.py +3 -4
  67. cirq/ops/pauli_string_raw_types_test.py +3 -3
  68. cirq/ops/pauli_string_test.py +3 -4
  69. cirq/ops/random_gate_channel_test.py +3 -3
  70. cirq/ops/raw_types.py +1 -1
  71. cirq/ops/raw_types_test.py +5 -5
  72. cirq/ops/three_qubit_gates.py +12 -8
  73. cirq/protocols/act_on_protocol_test.py +9 -9
  74. cirq/protocols/apply_channel_protocol.py +9 -6
  75. cirq/protocols/apply_unitary_protocol_test.py +1 -1
  76. cirq/protocols/equal_up_to_global_phase_protocol_test.py +2 -2
  77. cirq/protocols/has_stabilizer_effect_protocol.py +52 -6
  78. cirq/protocols/has_stabilizer_effect_protocol_test.py +21 -8
  79. cirq/protocols/has_unitary_protocol_test.py +1 -3
  80. cirq/protocols/json_serialization.py +6 -6
  81. cirq/protocols/json_serialization_test.py +7 -14
  82. cirq/protocols/json_test_data/InsertionNoiseModel.json +91 -0
  83. cirq/protocols/json_test_data/InsertionNoiseModel.repr +4 -0
  84. cirq/protocols/json_test_data/OpIdentifier.json +45 -10
  85. cirq/protocols/json_test_data/OpIdentifier.repr +7 -1
  86. cirq/protocols/json_test_data/spec.py +4 -0
  87. cirq/protocols/measurement_key_protocol_test.py +1 -1
  88. cirq/protocols/unitary_protocol_test.py +13 -16
  89. cirq/qis/clifford_tableau.py +7 -8
  90. cirq/qis/measures.py +1 -1
  91. cirq/qis/states.py +2 -3
  92. cirq/sim/__init__.py +2 -0
  93. cirq/sim/classical_simulator.py +107 -0
  94. cirq/sim/classical_simulator_test.py +207 -0
  95. cirq/sim/clifford/clifford_simulator_test.py +7 -7
  96. cirq/sim/clifford/stabilizer_simulation_state.py +2 -2
  97. cirq/sim/clifford/stabilizer_state_ch_form.py +7 -7
  98. cirq/sim/density_matrix_simulation_state.py +19 -4
  99. cirq/sim/density_matrix_simulator_test.py +5 -13
  100. cirq/sim/simulation_state_test.py +13 -14
  101. cirq/sim/simulator_test.py +6 -9
  102. cirq/sim/state_vector_simulation_state.py +1 -1
  103. cirq/study/resolver.py +41 -41
  104. cirq/study/resolver_test.py +13 -12
  105. cirq/testing/__init__.py +4 -1
  106. cirq/testing/circuit_compare.py +1 -1
  107. cirq/testing/circuit_compare_test.py +11 -11
  108. cirq/testing/consistent_controlled_gate_op.py +15 -1
  109. cirq/testing/consistent_controlled_gate_op_test.py +12 -3
  110. cirq/testing/consistent_decomposition.py +0 -1
  111. cirq/testing/consistent_protocols.py +6 -1
  112. cirq/testing/consistent_protocols_test.py +5 -10
  113. cirq/testing/consistent_qasm.py +2 -4
  114. cirq/testing/consistent_qasm_test.py +2 -3
  115. cirq/testing/consistent_specified_has_unitary_test.py +1 -3
  116. cirq/testing/equals_tester.py +1 -1
  117. cirq/testing/equals_tester_test.py +5 -5
  118. cirq/testing/equivalent_repr_eval_test.py +1 -3
  119. cirq/testing/gate_features_test.py +6 -6
  120. cirq/testing/order_tester_test.py +1 -3
  121. cirq/testing/random_circuit_test.py +1 -3
  122. cirq/transformers/__init__.py +3 -0
  123. cirq/transformers/analytical_decompositions/__init__.py +1 -0
  124. cirq/transformers/analytical_decompositions/three_qubit_decomposition.py +1 -2
  125. cirq/transformers/analytical_decompositions/three_qubit_decomposition_test.py +2 -5
  126. cirq/transformers/analytical_decompositions/two_qubit_state_preparation.py +38 -0
  127. cirq/transformers/analytical_decompositions/two_qubit_state_preparation_test.py +18 -0
  128. cirq/transformers/expand_composite_test.py +4 -4
  129. cirq/transformers/heuristic_decompositions/gate_tabulation_math_utils.py +1 -1
  130. cirq/transformers/heuristic_decompositions/two_qubit_gate_tabulation.py +1 -2
  131. cirq/transformers/merge_k_qubit_gates_test.py +2 -2
  132. cirq/transformers/qubit_management_transformers.py +177 -0
  133. cirq/transformers/qubit_management_transformers_test.py +250 -0
  134. cirq/transformers/routing/route_circuit_cqc.py +23 -4
  135. cirq/transformers/routing/route_circuit_cqc_test.py +42 -0
  136. cirq/transformers/stratify.py +10 -11
  137. cirq/transformers/target_gatesets/compilation_target_gateset_test.py +10 -10
  138. cirq/transformers/target_gatesets/cz_gateset_test.py +8 -10
  139. cirq/transformers/transformer_primitives.py +138 -28
  140. cirq/value/abc_alt_test.py +4 -4
  141. cirq/value/duration.py +68 -37
  142. cirq/value/duration_test.py +2 -0
  143. cirq/value/measurement_key_test.py +1 -1
  144. cirq/value/product_state.py +4 -8
  145. cirq/value/value_equality_attr.py +12 -5
  146. cirq/vis/heatmap.py +7 -4
  147. cirq/vis/heatmap_test.py +14 -4
  148. cirq/vis/histogram.py +4 -4
  149. cirq/vis/state_histogram.py +10 -6
  150. cirq/vis/state_histogram_test.py +2 -0
  151. cirq/work/observable_measurement_data_test.py +1 -1
  152. cirq/work/observable_measurement_test.py +2 -2
  153. cirq/work/zeros_sampler.py +1 -1
  154. {cirq_core-1.2.0.dev20230717225858.dist-info → cirq_core-1.3.0.dist-info}/METADATA +11 -19
  155. {cirq_core-1.2.0.dev20230717225858.dist-info → cirq_core-1.3.0.dist-info}/RECORD +158 -150
  156. {cirq_core-1.2.0.dev20230717225858.dist-info → cirq_core-1.3.0.dist-info}/WHEEL +1 -1
  157. {cirq_core-1.2.0.dev20230717225858.dist-info → cirq_core-1.3.0.dist-info}/LICENSE +0 -0
  158. {cirq_core-1.2.0.dev20230717225858.dist-info → cirq_core-1.3.0.dist-info}/top_level.txt +0 -0
cirq/__init__.py CHANGED
@@ -224,6 +224,7 @@ from cirq.ops import (
224
224
  givens,
225
225
  GlobalPhaseGate,
226
226
  global_phase_operation,
227
+ GreedyQubitManager,
227
228
  H,
228
229
  HPowGate,
229
230
  I,
@@ -301,6 +302,7 @@ from cirq.ops import (
301
302
  ry,
302
303
  rz,
303
304
  S,
305
+ SimpleQubitManager,
304
306
  SingleQubitCliffordGate,
305
307
  SingleQubitPauliStringGateOperation,
306
308
  SQRT_ISWAP,
@@ -356,6 +358,7 @@ from cirq.transformers import (
356
358
  is_negligible_turn,
357
359
  LineInitialMapper,
358
360
  MappingManager,
361
+ map_clean_and_borrowable_qubits,
359
362
  map_moments,
360
363
  map_operations,
361
364
  map_operations_and_unroll,
@@ -370,6 +373,7 @@ from cirq.transformers import (
370
373
  optimize_for_target_gateset,
371
374
  parameterized_2q_op_to_sqrt_iswap_operations,
372
375
  prepare_two_qubit_state_using_cz,
376
+ prepare_two_qubit_state_using_iswap,
373
377
  prepare_two_qubit_state_using_sqrt_iswap,
374
378
  quantum_shannon_decomposition,
375
379
  RouteCQC,
@@ -437,6 +441,7 @@ from cirq.qis import (
437
441
 
438
442
  from cirq.sim import (
439
443
  CIRCUIT_LIKE,
444
+ ClassicalStateSimulator,
440
445
  CliffordSimulator,
441
446
  CliffordState,
442
447
  CliffordSimulatorStepResult,
cirq/_compat.py CHANGED
@@ -191,6 +191,9 @@ def proper_repr(value: Any) -> str:
191
191
  if isinstance(value, Dict):
192
192
  return '{' + ','.join(f"{proper_repr(k)}: {proper_repr(v)}" for k, v in value.items()) + '}'
193
193
 
194
+ if hasattr(value, "__qualname__"):
195
+ return f"{value.__module__}.{value.__qualname__}"
196
+
194
197
  return repr(value)
195
198
 
196
199
 
@@ -400,23 +403,36 @@ def deprecated_parameter(
400
403
  _validate_deadline(deadline)
401
404
 
402
405
  def decorator(func: Callable) -> Callable:
406
+ def deprecation_warning():
407
+ qualname = func.__qualname__ if func_name is None else func_name
408
+ _warn_or_error(
409
+ f'The {parameter_desc} parameter of {qualname} was '
410
+ f'used but is deprecated.\n'
411
+ f'It will be removed in cirq {deadline}.\n'
412
+ f'{fix}\n'
413
+ )
414
+
403
415
  @functools.wraps(func)
404
416
  def decorated_func(*args, **kwargs) -> Any:
405
417
  if match(args, kwargs):
406
418
  if rewrite is not None:
407
419
  args, kwargs = rewrite(args, kwargs)
420
+ deprecation_warning()
421
+ return func(*args, **kwargs)
408
422
 
409
- qualname = func.__qualname__ if func_name is None else func_name
410
- _warn_or_error(
411
- f'The {parameter_desc} parameter of {qualname} was '
412
- f'used but is deprecated.\n'
413
- f'It will be removed in cirq {deadline}.\n'
414
- f'{fix}\n'
415
- )
423
+ @functools.wraps(func)
424
+ async def async_decorated_func(*args, **kwargs) -> Any:
425
+ if match(args, kwargs):
426
+ if rewrite is not None:
427
+ args, kwargs = rewrite(args, kwargs)
428
+ deprecation_warning()
416
429
 
417
- return func(*args, **kwargs)
430
+ return await func(*args, **kwargs)
418
431
 
419
- return decorated_func
432
+ if inspect.iscoroutinefunction(func):
433
+ return async_decorated_func
434
+ else:
435
+ return decorated_func
420
436
 
421
437
  return decorator
422
438
 
@@ -436,13 +452,12 @@ def deprecate_attributes(module_name: str, deprecated_attributes: Dict[str, Tupl
436
452
  will cause a warning for these deprecated attributes.
437
453
  """
438
454
 
439
- for (deadline, _) in deprecated_attributes.values():
455
+ for deadline, _ in deprecated_attributes.values():
440
456
  _validate_deadline(deadline)
441
457
 
442
458
  module = sys.modules[module_name]
443
459
 
444
460
  class Wrapped(ModuleType):
445
-
446
461
  __dict__ = module.__dict__
447
462
 
448
463
  # Workaround for: https://github.com/python/mypy/issues/8083
cirq/_compat_test.py CHANGED
@@ -14,6 +14,7 @@
14
14
  import collections
15
15
  import dataclasses
16
16
  import importlib.metadata
17
+ import inspect
17
18
  import logging
18
19
  import multiprocessing
19
20
  import os
@@ -26,7 +27,7 @@ from typing import Any, Callable, Dict, Optional, Tuple
26
27
  from importlib.machinery import ModuleSpec
27
28
  from unittest import mock
28
29
 
29
-
30
+ import duet
30
31
  import numpy as np
31
32
  import pandas as pd
32
33
  import pytest
@@ -263,6 +264,40 @@ def test_deprecated_parameter():
263
264
  # pylint: enable=unused-variable
264
265
 
265
266
 
267
+ @duet.sync
268
+ async def test_deprecated_parameter_async_function():
269
+ @deprecated_parameter(
270
+ deadline='v1.2',
271
+ fix='Double it yourself.',
272
+ func_name='test_func',
273
+ parameter_desc='double_count',
274
+ match=lambda args, kwargs: 'double_count' in kwargs,
275
+ rewrite=lambda args, kwargs: (args, {'new_count': kwargs['double_count'] * 2}),
276
+ )
277
+ async def f(new_count):
278
+ return new_count
279
+
280
+ assert inspect.iscoroutinefunction(f)
281
+
282
+ # Does not warn on usual use.
283
+ with cirq.testing.assert_logs(count=0):
284
+ assert await f(1) == 1
285
+ assert await f(new_count=1) == 1
286
+
287
+ with cirq.testing.assert_deprecated(
288
+ '_compat_test.py:',
289
+ 'double_count parameter of test_func was used',
290
+ 'will be removed in cirq v1.2',
291
+ 'Double it yourself.',
292
+ deadline='v1.2',
293
+ ):
294
+ # pylint: disable=unexpected-keyword-arg
295
+ # pylint: disable=no-value-for-parameter
296
+ assert await f(double_count=1) == 2
297
+ # pylint: enable=no-value-for-parameter
298
+ # pylint: enable=unexpected-keyword-arg
299
+
300
+
266
301
  def test_wrap_module():
267
302
  my_module = types.ModuleType('my_module', 'my doc string')
268
303
  my_module.foo = 'foo'
@@ -781,8 +816,7 @@ def test_deprecated_module_deadline_validation():
781
816
 
782
817
  def _test_broken_module_1_inner():
783
818
  with pytest.raises(
784
- DeprecatedModuleImportError,
785
- match="missing_module cannot be imported. " "The typical reasons",
819
+ DeprecatedModuleImportError, match="missing_module cannot be imported. The typical reasons"
786
820
  ):
787
821
  # pylint: disable=unused-import
788
822
  import cirq.testing._compat_test_data.broken_ref as br # type: ignore
cirq/_version.py CHANGED
@@ -1 +1,31 @@
1
- __version__ = "1.2.0.dev20230717225858"
1
+ # Copyright 2018 The Cirq Developers
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Define version number here, read it from setup.py automatically,
16
+ and warn users that the latest version of cirq uses python 3.9+"""
17
+
18
+ import sys
19
+
20
+ if sys.version_info < (3, 9, 0): # pragma: no cover
21
+ raise SystemError(
22
+ "You installed the latest version of cirq but aren't on python 3.9+.\n"
23
+ 'To fix this error, you need to either:\n'
24
+ '\n'
25
+ 'A) Update to python 3.9 or later.\n'
26
+ '- OR -\n'
27
+ 'B) Explicitly install an older deprecated-but-compatible version '
28
+ 'of cirq (e.g. "python -m pip install cirq==1.1.*")'
29
+ )
30
+
31
+ __version__ = "1.3.0"
cirq/_version_test.py CHANGED
@@ -3,4 +3,4 @@ import cirq
3
3
 
4
4
 
5
5
  def test_version():
6
- assert cirq.__version__ == "1.2.0.dev"
6
+ assert cirq.__version__ == "1.3.0.dev"
cirq/circuits/circuit.py CHANGED
@@ -149,11 +149,26 @@ class AbstractCircuit(abc.ABC):
149
149
  """Create a circuit from moment op trees.
150
150
 
151
151
  Args:
152
- *moments: Op tree for each moment.
152
+ *moments: Op tree for each moment. If an op tree is a moment, it
153
+ will be included directly in the new circuit. If an op tree is
154
+ a circuit, it will be frozen, wrapped in a CircuitOperation, and
155
+ included in its own moment in the new circuit. Otherwise, the
156
+ op tree will be passed to `cirq.Moment` to create a new moment
157
+ which is then included in the new circuit. Note that in the
158
+ latter case we have the normal restriction that operations in a
159
+ moment must be applied to disjoint sets of qubits.
153
160
  """
154
- return cls._from_moments(
155
- moment if isinstance(moment, Moment) else Moment(moment) for moment in moments
156
- )
161
+ return cls._from_moments(cls._make_moments(moments))
162
+
163
+ @staticmethod
164
+ def _make_moments(moments: Iterable['cirq.OP_TREE']) -> Iterator['cirq.Moment']:
165
+ for m in moments:
166
+ if isinstance(m, Moment):
167
+ yield m
168
+ elif isinstance(m, AbstractCircuit):
169
+ yield Moment(m.freeze().to_op())
170
+ else:
171
+ yield Moment(m)
157
172
 
158
173
  @classmethod
159
174
  @abc.abstractmethod
@@ -173,28 +188,20 @@ class AbstractCircuit(abc.ABC):
173
188
  def moments(self) -> Sequence['cirq.Moment']:
174
189
  pass
175
190
 
191
+ @abc.abstractmethod
176
192
  def freeze(self) -> 'cirq.FrozenCircuit':
177
193
  """Creates a FrozenCircuit from this circuit.
178
194
 
179
195
  If 'self' is a FrozenCircuit, the original object is returned.
180
196
  """
181
- from cirq.circuits import FrozenCircuit
182
-
183
- if isinstance(self, FrozenCircuit):
184
- return self
185
-
186
- return FrozenCircuit(self, strategy=InsertStrategy.EARLIEST)
187
197
 
198
+ @abc.abstractmethod
188
199
  def unfreeze(self, copy: bool = True) -> 'cirq.Circuit':
189
200
  """Creates a Circuit from this circuit.
190
201
 
191
202
  Args:
192
203
  copy: If True and 'self' is a Circuit, returns a copy that circuit.
193
204
  """
194
- if isinstance(self, Circuit):
195
- return Circuit.copy(self) if copy else self
196
-
197
- return Circuit(self, strategy=InsertStrategy.EARLIEST)
198
205
 
199
206
  def __bool__(self):
200
207
  return bool(self.moments)
@@ -272,12 +279,15 @@ class AbstractCircuit(abc.ABC):
272
279
  def __str__(self) -> str:
273
280
  return self.to_text_diagram()
274
281
 
275
- def __repr__(self) -> str:
276
- cls_name = self.__class__.__name__
282
+ def _repr_args(self) -> str:
277
283
  args = []
278
284
  if self.moments:
279
285
  args.append(_list_repr_with_indented_item_lines(self.moments))
280
- return f'cirq.{cls_name}({", ".join(args)})'
286
+ return f'{", ".join(args)}'
287
+
288
+ def __repr__(self) -> str:
289
+ cls_name = self.__class__.__name__
290
+ return f'cirq.{cls_name}({self._repr_args()})'
281
291
 
282
292
  def _repr_pretty_(self, p: Any, cycle: bool) -> None:
283
293
  """Print ASCII diagram in Jupyter."""
@@ -804,6 +814,9 @@ class AbstractCircuit(abc.ABC):
804
814
  """
805
815
  return protocols.is_measurement(self)
806
816
 
817
+ def _is_measurement_(self) -> bool:
818
+ return any(protocols.is_measurement(op) for op in self.all_operations())
819
+
807
820
  def are_all_measurements_terminal(self) -> bool:
808
821
  """Whether all measurement gates are at the end of the circuit.
809
822
 
@@ -1365,8 +1378,7 @@ class AbstractCircuit(abc.ABC):
1365
1378
  self._to_qasm_output(header, precision, qubit_order).save(file_path)
1366
1379
 
1367
1380
  def _json_dict_(self):
1368
- ret = protocols.obj_to_dict_helper(self, ['moments'])
1369
- return ret
1381
+ return protocols.obj_to_dict_helper(self, ['moments'])
1370
1382
 
1371
1383
  @classmethod
1372
1384
  def _from_json_dict_(cls, moments, **kwargs):
@@ -1462,14 +1474,14 @@ class AbstractCircuit(abc.ABC):
1462
1474
 
1463
1475
  Beware that this method is *not* associative. For example:
1464
1476
 
1465
- >>> a, b = cirq.LineQubit.range(2)
1466
- >>> A = cirq.Circuit(cirq.H(a))
1467
- >>> B = cirq.Circuit(cirq.H(b))
1468
- >>> f = cirq.Circuit.concat_ragged
1469
- >>> f(f(A, B), A) == f(A, f(B, A))
1470
- False
1471
- >>> len(f(f(f(A, B), A), B)) == len(f(f(A, f(B, A)), B))
1472
- False
1477
+ >>> a, b = cirq.LineQubit.range(2)
1478
+ >>> A = cirq.Circuit(cirq.H(a))
1479
+ >>> B = cirq.Circuit(cirq.H(b))
1480
+ >>> f = cirq.Circuit.concat_ragged
1481
+ >>> f(f(A, B), A) == f(A, f(B, A))
1482
+ False
1483
+ >>> len(f(f(f(A, B), A), B)) == len(f(f(A, f(B, A)), B))
1484
+ False
1473
1485
 
1474
1486
  Args:
1475
1487
  *circuits: The circuits to concatenate.
@@ -1741,6 +1753,16 @@ class Circuit(AbstractCircuit):
1741
1753
  circuit.
1742
1754
  """
1743
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
+
1744
1766
  flattened_contents = tuple(ops.flatten_to_ops_or_moments(contents))
1745
1767
  if all(isinstance(c, Moment) for c in flattened_contents):
1746
1768
  self._moments[:] = cast(Iterable[Moment], flattened_contents)
@@ -1751,6 +1773,14 @@ class Circuit(AbstractCircuit):
1751
1773
  else:
1752
1774
  self.append(flattened_contents, strategy=strategy)
1753
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
+
1754
1784
  @classmethod
1755
1785
  def _from_moments(cls, moments: Iterable['cirq.Moment']) -> 'Circuit':
1756
1786
  new_circuit = Circuit()
@@ -1791,7 +1821,6 @@ class Circuit(AbstractCircuit):
1791
1821
 
1792
1822
  # "mop" means current moment-or-operation
1793
1823
  for mop in ops.flatten_to_ops_or_moments(contents):
1794
-
1795
1824
  # Identify the index of the moment to place this `mop` into.
1796
1825
  placement_index = get_earliest_accommodating_moment_index(
1797
1826
  mop, qubit_indices, mkey_indices, ckey_indices, length
@@ -1814,6 +1843,41 @@ class Circuit(AbstractCircuit):
1814
1843
  def __copy__(self) -> 'cirq.Circuit':
1815
1844
  return self.copy()
1816
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
+
1817
1881
  def copy(self) -> 'Circuit':
1818
1882
  """Return a copy of this circuit."""
1819
1883
  copied_circuit = Circuit()
@@ -1839,11 +1903,13 @@ class Circuit(AbstractCircuit):
1839
1903
  raise TypeError('Can only assign Moments into Circuits.')
1840
1904
 
1841
1905
  self._moments[key] = value
1906
+ self._mutated()
1842
1907
 
1843
1908
  # pylint: enable=function-redefined
1844
1909
 
1845
1910
  def __delitem__(self, key: Union[int, slice]):
1846
1911
  del self._moments[key]
1912
+ self._mutated()
1847
1913
 
1848
1914
  def __iadd__(self, other):
1849
1915
  self.append(other)
@@ -1872,6 +1938,7 @@ class Circuit(AbstractCircuit):
1872
1938
  if not isinstance(repetitions, (int, np.integer)):
1873
1939
  return NotImplemented
1874
1940
  self._moments *= int(repetitions)
1941
+ self._mutated()
1875
1942
  return self
1876
1943
 
1877
1944
  def __mul__(self, repetitions: _INT_TYPE):
@@ -2015,6 +2082,7 @@ class Circuit(AbstractCircuit):
2015
2082
 
2016
2083
  if strategy is InsertStrategy.NEW or strategy is InsertStrategy.NEW_THEN_INLINE:
2017
2084
  self._moments.insert(splitter_index, Moment())
2085
+ self._mutated()
2018
2086
  return splitter_index
2019
2087
 
2020
2088
  if strategy is InsertStrategy.INLINE:
@@ -2082,6 +2150,7 @@ class Circuit(AbstractCircuit):
2082
2150
  k = max(k, p + 1)
2083
2151
  if strategy is InsertStrategy.NEW_THEN_INLINE:
2084
2152
  strategy = InsertStrategy.INLINE
2153
+ self._mutated()
2085
2154
  return k
2086
2155
 
2087
2156
  def insert_into_range(self, operations: 'cirq.OP_TREE', start: int, end: int) -> int:
@@ -2118,6 +2187,7 @@ class Circuit(AbstractCircuit):
2118
2187
 
2119
2188
  self._moments[i] = self._moments[i].with_operation(op)
2120
2189
  op_index += 1
2190
+ self._mutated()
2121
2191
 
2122
2192
  if op_index >= len(flat_ops):
2123
2193
  return end
@@ -2163,6 +2233,7 @@ class Circuit(AbstractCircuit):
2163
2233
  if n_new_moments > 0:
2164
2234
  insert_index = min(late_frontier.values())
2165
2235
  self._moments[insert_index:insert_index] = [Moment()] * n_new_moments
2236
+ self._mutated()
2166
2237
  for q in update_qubits:
2167
2238
  if early_frontier.get(q, 0) > insert_index:
2168
2239
  early_frontier[q] += n_new_moments
@@ -2189,13 +2260,12 @@ class Circuit(AbstractCircuit):
2189
2260
  if len(operations) != len(insertion_indices):
2190
2261
  raise ValueError('operations and insertion_indices must have the same length.')
2191
2262
  self._moments += [Moment() for _ in range(1 + max(insertion_indices) - len(self))]
2263
+ self._mutated()
2192
2264
  moment_to_ops: Dict[int, List['cirq.Operation']] = defaultdict(list)
2193
2265
  for op_index, moment_index in enumerate(insertion_indices):
2194
2266
  moment_to_ops[moment_index].append(operations[op_index])
2195
2267
  for moment_index, new_ops in moment_to_ops.items():
2196
- self._moments[moment_index] = Moment(
2197
- self._moments[moment_index].operations + tuple(new_ops)
2198
- )
2268
+ self._moments[moment_index] = self._moments[moment_index].with_operations(*new_ops)
2199
2269
 
2200
2270
  def insert_at_frontier(
2201
2271
  self,
@@ -2257,6 +2327,7 @@ class Circuit(AbstractCircuit):
2257
2327
  old_op for old_op in copy._moments[i].operations if op != old_op
2258
2328
  )
2259
2329
  self._moments = copy._moments
2330
+ self._mutated()
2260
2331
 
2261
2332
  def batch_replace(
2262
2333
  self, replacements: Iterable[Tuple[int, 'cirq.Operation', 'cirq.Operation']]
@@ -2281,6 +2352,7 @@ class Circuit(AbstractCircuit):
2281
2352
  old_op if old_op != op else new_op for old_op in copy._moments[i].operations
2282
2353
  )
2283
2354
  self._moments = copy._moments
2355
+ self._mutated()
2284
2356
 
2285
2357
  def batch_insert_into(self, insert_intos: Iterable[Tuple[int, 'cirq.OP_TREE']]) -> None:
2286
2358
  """Inserts operations into empty spaces in existing moments.
@@ -2301,6 +2373,7 @@ class Circuit(AbstractCircuit):
2301
2373
  for i, insertions in insert_intos:
2302
2374
  copy._moments[i] = copy._moments[i].with_operations(insertions)
2303
2375
  self._moments = copy._moments
2376
+ self._mutated()
2304
2377
 
2305
2378
  def batch_insert(self, insertions: Iterable[Tuple[int, 'cirq.OP_TREE']]) -> None:
2306
2379
  """Applies a batched insert operation to the circuit.
@@ -2335,6 +2408,7 @@ class Circuit(AbstractCircuit):
2335
2408
  if next_index > insert_index:
2336
2409
  shift += next_index - insert_index
2337
2410
  self._moments = copy._moments
2411
+ self._mutated()
2338
2412
 
2339
2413
  def append(
2340
2414
  self,
@@ -2365,6 +2439,7 @@ class Circuit(AbstractCircuit):
2365
2439
  for k in moment_indices:
2366
2440
  if 0 <= k < len(self._moments):
2367
2441
  self._moments[k] = self._moments[k].without_operations_touching(qubits)
2442
+ self._mutated()
2368
2443
 
2369
2444
  @property
2370
2445
  def moments(self) -> Sequence['cirq.Moment']:
@@ -2450,7 +2525,6 @@ def _draw_moment_annotations(
2450
2525
  first_annotation_row: int,
2451
2526
  transpose: bool,
2452
2527
  ):
2453
-
2454
2528
  for k, annotation in enumerate(_get_moment_annotations(moment)):
2455
2529
  args = protocols.CircuitDiagramInfoArgs(
2456
2530
  known_qubits=(),
@@ -61,9 +61,9 @@ def _full_join_string_lists(
61
61
  list1: Optional[Sequence[str]], list2: Optional[Sequence[str]]
62
62
  ) -> Optional[Sequence[str]]:
63
63
  if list1 is None and list2 is None:
64
- return None # coverage: ignore
64
+ return None # pragma: no cover
65
65
  if list1 is None:
66
- return list2 # coverage: ignore
66
+ return list2 # pragma: no cover
67
67
  if list2 is None:
68
68
  return list1
69
69
  return [f'{first}{REPETITION_ID_SEPARATOR}{second}' for first in list1 for second in list2]
@@ -351,7 +351,7 @@ def test_repeat_zero_times(add_measurements, use_repetition_ids, initial_reps):
351
351
 
352
352
 
353
353
  def test_no_repetition_ids():
354
- def default_repetition_ids(self):
354
+ def default_repetition_ids(self): # pragma: no cover
355
355
  assert False, "Should not call default_repetition_ids"
356
356
 
357
357
  with mock.patch.object(circuit_operation, 'default_repetition_ids', new=default_repetition_ids):