cirq-core 1.1.0.dev20221220224914__py3-none-any.whl → 1.2.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 (228) hide show
  1. cirq/__init__.py +8 -0
  2. cirq/_compat.py +29 -4
  3. cirq/_compat_test.py +24 -26
  4. cirq/_version.py +32 -1
  5. cirq/_version_test.py +1 -1
  6. cirq/circuits/_block_diagram_drawer_test.py +4 -3
  7. cirq/circuits/circuit.py +109 -63
  8. cirq/circuits/circuit_operation.py +2 -3
  9. cirq/circuits/circuit_operation_test.py +4 -4
  10. cirq/circuits/circuit_test.py +11 -0
  11. cirq/circuits/frozen_circuit.py +13 -1
  12. cirq/circuits/frozen_circuit_test.py +5 -1
  13. cirq/circuits/moment.py +39 -14
  14. cirq/circuits/moment_test.py +7 -0
  15. cirq/circuits/text_diagram_drawer.py +1 -1
  16. cirq/circuits/text_diagram_drawer_test.py +3 -7
  17. cirq/contrib/acquaintance/bipartite.py +1 -1
  18. cirq/contrib/acquaintance/devices.py +2 -2
  19. cirq/contrib/acquaintance/executor.py +5 -2
  20. cirq/contrib/acquaintance/gates.py +3 -2
  21. cirq/contrib/acquaintance/permutation.py +13 -2
  22. cirq/contrib/acquaintance/testing.py +3 -5
  23. cirq/contrib/paulistring/recombine.py +3 -6
  24. cirq/contrib/qasm_import/_parser.py +17 -21
  25. cirq/contrib/qasm_import/_parser_test.py +30 -45
  26. cirq/contrib/qcircuit/qcircuit_test.py +3 -7
  27. cirq/contrib/quantum_volume/quantum_volume.py +3 -3
  28. cirq/contrib/quimb/mps_simulator.py +1 -1
  29. cirq/contrib/quimb/state_vector.py +2 -0
  30. cirq/contrib/quirk/quirk_gate.py +1 -0
  31. cirq/contrib/svg/svg.py +4 -7
  32. cirq/contrib/svg/svg_test.py +29 -1
  33. cirq/devices/grid_qubit.py +26 -28
  34. cirq/devices/grid_qubit_test.py +21 -5
  35. cirq/devices/line_qubit.py +10 -12
  36. cirq/devices/line_qubit_test.py +9 -2
  37. cirq/devices/named_topologies.py +1 -1
  38. cirq/devices/noise_model.py +4 -1
  39. cirq/devices/superconducting_qubits_noise_properties.py +1 -3
  40. cirq/experiments/n_qubit_tomography.py +1 -1
  41. cirq/experiments/qubit_characterizations.py +2 -2
  42. cirq/experiments/single_qubit_readout_calibration.py +1 -1
  43. cirq/experiments/t2_decay_experiment.py +1 -1
  44. cirq/experiments/xeb_simulation_test.py +2 -2
  45. cirq/interop/quirk/cells/testing.py +1 -1
  46. cirq/json_resolver_cache.py +1 -0
  47. cirq/linalg/__init__.py +2 -0
  48. cirq/linalg/decompositions_test.py +4 -4
  49. cirq/linalg/diagonalize_test.py +5 -6
  50. cirq/linalg/transformations.py +72 -9
  51. cirq/linalg/transformations_test.py +23 -7
  52. cirq/ops/__init__.py +4 -0
  53. cirq/ops/arithmetic_operation.py +4 -6
  54. cirq/ops/classically_controlled_operation.py +10 -3
  55. cirq/ops/clifford_gate.py +1 -7
  56. cirq/ops/common_channels.py +21 -15
  57. cirq/ops/common_gate_families.py +2 -3
  58. cirq/ops/common_gates.py +48 -11
  59. cirq/ops/common_gates_test.py +4 -0
  60. cirq/ops/controlled_gate.py +44 -18
  61. cirq/ops/controlled_operation.py +13 -5
  62. cirq/ops/dense_pauli_string.py +14 -19
  63. cirq/ops/diagonal_gate.py +3 -4
  64. cirq/ops/eigen_gate.py +8 -10
  65. cirq/ops/eigen_gate_test.py +6 -0
  66. cirq/ops/gate_operation.py +11 -6
  67. cirq/ops/gate_operation_test.py +11 -2
  68. cirq/ops/gateset.py +2 -1
  69. cirq/ops/gateset_test.py +38 -5
  70. cirq/ops/global_phase_op.py +28 -2
  71. cirq/ops/global_phase_op_test.py +21 -0
  72. cirq/ops/identity.py +1 -1
  73. cirq/ops/kraus_channel_test.py +2 -2
  74. cirq/ops/linear_combinations.py +7 -6
  75. cirq/ops/linear_combinations_test.py +26 -10
  76. cirq/ops/matrix_gates.py +8 -4
  77. cirq/ops/matrix_gates_test.py +25 -3
  78. cirq/ops/measure_util.py +13 -5
  79. cirq/ops/measure_util_test.py +8 -2
  80. cirq/ops/measurement_gate.py +1 -1
  81. cirq/ops/measurement_gate_test.py +9 -4
  82. cirq/ops/mixed_unitary_channel_test.py +4 -4
  83. cirq/ops/named_qubit.py +2 -4
  84. cirq/ops/parity_gates.py +5 -1
  85. cirq/ops/parity_gates_test.py +6 -0
  86. cirq/ops/pauli_gates.py +9 -9
  87. cirq/ops/pauli_string.py +4 -2
  88. cirq/ops/pauli_string_raw_types.py +4 -11
  89. cirq/ops/pauli_string_test.py +13 -13
  90. cirq/ops/pauli_sum_exponential.py +6 -1
  91. cirq/ops/qubit_manager.py +97 -0
  92. cirq/ops/qubit_manager_test.py +66 -0
  93. cirq/ops/raw_types.py +75 -33
  94. cirq/ops/raw_types_test.py +34 -0
  95. cirq/ops/three_qubit_gates.py +16 -10
  96. cirq/ops/three_qubit_gates_test.py +4 -2
  97. cirq/ops/two_qubit_diagonal_gate.py +3 -3
  98. cirq/ops/wait_gate.py +1 -1
  99. cirq/protocols/__init__.py +1 -0
  100. cirq/protocols/act_on_protocol.py +3 -3
  101. cirq/protocols/act_on_protocol_test.py +5 -5
  102. cirq/protocols/apply_channel_protocol.py +9 -8
  103. cirq/protocols/apply_mixture_protocol.py +8 -8
  104. cirq/protocols/apply_mixture_protocol_test.py +1 -1
  105. cirq/protocols/apply_unitary_protocol.py +66 -19
  106. cirq/protocols/apply_unitary_protocol_test.py +50 -0
  107. cirq/protocols/circuit_diagram_info_protocol.py +7 -9
  108. cirq/protocols/decompose_protocol.py +167 -125
  109. cirq/protocols/decompose_protocol_test.py +132 -2
  110. cirq/protocols/has_stabilizer_effect_protocol.py +2 -1
  111. cirq/protocols/inverse_protocol.py +2 -2
  112. cirq/protocols/json_serialization_test.py +3 -3
  113. cirq/protocols/json_test_data/Linspace.json +20 -7
  114. cirq/protocols/json_test_data/Linspace.repr +4 -1
  115. cirq/protocols/json_test_data/Points.json +19 -8
  116. cirq/protocols/json_test_data/Points.repr +4 -1
  117. cirq/protocols/json_test_data/Result.repr_inward +1 -1
  118. cirq/protocols/json_test_data/ResultDict.repr +1 -1
  119. cirq/protocols/json_test_data/ResultDict.repr_inward +1 -1
  120. cirq/protocols/json_test_data/TrialResult.repr_inward +1 -1
  121. cirq/protocols/json_test_data/XPowGate.json +13 -5
  122. cirq/protocols/json_test_data/XPowGate.repr +1 -1
  123. cirq/protocols/json_test_data/ZPowGate.json +13 -5
  124. cirq/protocols/json_test_data/ZPowGate.repr +1 -1
  125. cirq/protocols/json_test_data/ZipLongest.json +19 -0
  126. cirq/protocols/json_test_data/ZipLongest.repr +1 -0
  127. cirq/protocols/json_test_data/spec.py +1 -0
  128. cirq/protocols/kraus_protocol.py +3 -4
  129. cirq/protocols/measurement_key_protocol.py +3 -1
  130. cirq/protocols/mixture_protocol.py +3 -2
  131. cirq/protocols/phase_protocol.py +3 -3
  132. cirq/protocols/pow_protocol.py +1 -2
  133. cirq/protocols/qasm.py +4 -4
  134. cirq/protocols/qid_shape_protocol.py +8 -8
  135. cirq/protocols/resolve_parameters.py +8 -3
  136. cirq/protocols/resolve_parameters_test.py +3 -3
  137. cirq/protocols/unitary_protocol.py +19 -11
  138. cirq/protocols/unitary_protocol_test.py +37 -0
  139. cirq/qis/channels.py +1 -1
  140. cirq/qis/clifford_tableau.py +4 -5
  141. cirq/qis/quantum_state_representation.py +7 -9
  142. cirq/qis/states.py +21 -13
  143. cirq/qis/states_test.py +7 -0
  144. cirq/sim/clifford/clifford_simulator.py +3 -3
  145. cirq/sim/density_matrix_simulation_state.py +2 -1
  146. cirq/sim/density_matrix_simulator.py +1 -1
  147. cirq/sim/density_matrix_simulator_test.py +9 -5
  148. cirq/sim/density_matrix_utils.py +7 -32
  149. cirq/sim/mux.py +2 -2
  150. cirq/sim/simulation_state.py +58 -18
  151. cirq/sim/simulation_state_base.py +5 -2
  152. cirq/sim/simulation_state_test.py +121 -9
  153. cirq/sim/simulation_utils.py +59 -0
  154. cirq/sim/simulation_utils_test.py +32 -0
  155. cirq/sim/simulator.py +2 -1
  156. cirq/sim/simulator_base_test.py +3 -3
  157. cirq/sim/sparse_simulator.py +1 -1
  158. cirq/sim/sparse_simulator_test.py +5 -5
  159. cirq/sim/state_vector.py +7 -36
  160. cirq/sim/state_vector_simulation_state.py +18 -1
  161. cirq/sim/state_vector_simulator.py +3 -2
  162. cirq/sim/state_vector_simulator_test.py +24 -2
  163. cirq/sim/state_vector_test.py +46 -15
  164. cirq/study/__init__.py +1 -0
  165. cirq/study/flatten_expressions.py +2 -2
  166. cirq/study/resolver.py +2 -0
  167. cirq/study/resolver_test.py +1 -1
  168. cirq/study/result.py +1 -1
  169. cirq/study/sweeps.py +103 -9
  170. cirq/study/sweeps_test.py +64 -0
  171. cirq/testing/__init__.py +4 -0
  172. cirq/testing/circuit_compare.py +15 -18
  173. cirq/testing/consistent_act_on.py +4 -4
  174. cirq/testing/consistent_controlled_gate_op_test.py +1 -1
  175. cirq/testing/consistent_decomposition.py +11 -2
  176. cirq/testing/consistent_decomposition_test.py +8 -1
  177. cirq/testing/consistent_protocols.py +2 -0
  178. cirq/testing/consistent_protocols_test.py +8 -4
  179. cirq/testing/consistent_qasm.py +8 -15
  180. cirq/testing/consistent_specified_has_unitary.py +1 -1
  181. cirq/testing/consistent_unitary.py +85 -0
  182. cirq/testing/consistent_unitary_test.py +96 -0
  183. cirq/testing/equivalent_repr_eval.py +10 -10
  184. cirq/testing/json.py +3 -3
  185. cirq/testing/logs.py +1 -1
  186. cirq/testing/order_tester.py +4 -5
  187. cirq/testing/random_circuit.py +3 -5
  188. cirq/testing/sample_gates.py +79 -0
  189. cirq/testing/sample_gates_test.py +59 -0
  190. cirq/transformers/__init__.py +2 -0
  191. cirq/transformers/analytical_decompositions/__init__.py +8 -0
  192. cirq/transformers/analytical_decompositions/pauli_string_decomposition.py +130 -0
  193. cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py +58 -0
  194. cirq/transformers/analytical_decompositions/quantum_shannon_decomposition.py +230 -0
  195. cirq/transformers/analytical_decompositions/quantum_shannon_decomposition_test.py +112 -0
  196. cirq/transformers/analytical_decompositions/three_qubit_decomposition_test.py +1 -3
  197. cirq/transformers/analytical_decompositions/two_qubit_to_fsim.py +1 -1
  198. cirq/transformers/expand_composite.py +1 -1
  199. cirq/transformers/heuristic_decompositions/gate_tabulation_math_utils.py +4 -4
  200. cirq/transformers/measurement_transformers.py +4 -4
  201. cirq/transformers/merge_single_qubit_gates.py +17 -4
  202. cirq/transformers/routing/route_circuit_cqc.py +2 -2
  203. cirq/transformers/stratify.py +125 -62
  204. cirq/transformers/stratify_test.py +20 -16
  205. cirq/transformers/transformer_api.py +1 -1
  206. cirq/transformers/transformer_primitives.py +3 -2
  207. cirq/transformers/transformer_primitives_test.py +11 -0
  208. cirq/value/abc_alt.py +3 -2
  209. cirq/value/abc_alt_test.py +1 -0
  210. cirq/value/classical_data.py +10 -10
  211. cirq/value/digits.py +2 -2
  212. cirq/value/linear_dict.py +18 -19
  213. cirq/value/product_state.py +7 -6
  214. cirq/value/value_equality_attr.py +2 -2
  215. cirq/vis/heatmap.py +1 -1
  216. cirq/vis/heatmap_test.py +2 -2
  217. cirq/work/collector.py +2 -2
  218. cirq/work/observable_measurement_data.py +5 -5
  219. cirq/work/observable_readout_calibration.py +3 -1
  220. cirq/work/observable_settings.py +1 -1
  221. cirq/work/pauli_sum_collector.py +9 -8
  222. cirq/work/sampler.py +2 -0
  223. cirq/work/zeros_sampler.py +2 -2
  224. {cirq_core-1.1.0.dev20221220224914.dist-info → cirq_core-1.2.0.dist-info}/METADATA +7 -15
  225. {cirq_core-1.1.0.dev20221220224914.dist-info → cirq_core-1.2.0.dist-info}/RECORD +228 -214
  226. {cirq_core-1.1.0.dev20221220224914.dist-info → cirq_core-1.2.0.dist-info}/WHEEL +1 -1
  227. {cirq_core-1.1.0.dev20221220224914.dist-info → cirq_core-1.2.0.dist-info}/LICENSE +0 -0
  228. {cirq_core-1.1.0.dev20221220224914.dist-info → cirq_core-1.2.0.dist-info}/top_level.txt +0 -0
@@ -11,13 +11,17 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
-
14
+ import itertools
15
+ import dataclasses
16
+ import inspect
17
+ from collections import defaultdict
15
18
  from typing import (
16
19
  TYPE_CHECKING,
17
20
  Any,
18
21
  Callable,
19
22
  Dict,
20
23
  Iterable,
24
+ Iterator,
21
25
  List,
22
26
  Optional,
23
27
  overload,
@@ -26,7 +30,7 @@ from typing import (
26
30
  TypeVar,
27
31
  Union,
28
32
  )
29
- from collections import defaultdict
33
+ from typing_extensions import runtime_checkable
30
34
 
31
35
  from typing_extensions import Protocol
32
36
 
@@ -45,7 +49,19 @@ TError = TypeVar('TError', bound=Exception)
45
49
  RaiseTypeErrorIfNotProvided: Any = ([],)
46
50
 
47
51
  DecomposeResult = Union[None, NotImplementedType, 'cirq.OP_TREE']
48
- OpDecomposer = Callable[['cirq.Operation'], DecomposeResult]
52
+
53
+ _CONTEXT_COUNTER = itertools.count() # Use _reset_context_counter() to reset the counter.
54
+
55
+
56
+ @runtime_checkable
57
+ class OpDecomposerWithContext(Protocol):
58
+ def __call__(
59
+ self, __op: 'cirq.Operation', *, context: Optional['cirq.DecompositionContext'] = None
60
+ ) -> DecomposeResult:
61
+ ...
62
+
63
+
64
+ OpDecomposer = Union[Callable[['cirq.Operation'], DecomposeResult], OpDecomposerWithContext]
49
65
 
50
66
  DECOMPOSE_TARGET_GATESET = ops.Gateset(
51
67
  ops.XPowGate,
@@ -61,6 +77,18 @@ def _value_error_describing_bad_operation(op: 'cirq.Operation') -> ValueError:
61
77
  return ValueError(f"Operation doesn't satisfy the given `keep` but can't be decomposed: {op!r}")
62
78
 
63
79
 
80
+ @dataclasses.dataclass(frozen=True)
81
+ class DecompositionContext:
82
+ """Stores common configurable options for decomposing composite gates into simpler operations.
83
+
84
+ Args:
85
+ qubit_manager: A `cirq.QubitManager` instance to allocate clean / dirty ancilla qubits as
86
+ part of the decompose protocol.
87
+ """
88
+
89
+ qubit_manager: 'cirq.QubitManager'
90
+
91
+
64
92
  class SupportsDecompose(Protocol):
65
93
  """An object that can be decomposed into simpler operations.
66
94
 
@@ -104,6 +132,11 @@ class SupportsDecompose(Protocol):
104
132
  def _decompose_(self) -> DecomposeResult:
105
133
  pass
106
134
 
135
+ def _decompose_with_context_(
136
+ self, *, context: Optional[DecompositionContext] = None
137
+ ) -> DecomposeResult:
138
+ pass
139
+
107
140
 
108
141
  class SupportsDecomposeWithQubits(Protocol):
109
142
  """An object that can be decomposed into operations on given qubits.
@@ -127,6 +160,72 @@ class SupportsDecomposeWithQubits(Protocol):
127
160
  def _decompose_(self, qubits: Tuple['cirq.Qid', ...]) -> DecomposeResult:
128
161
  pass
129
162
 
163
+ def _decompose_with_context_(
164
+ self, qubits: Tuple['cirq.Qid', ...], *, context: Optional[DecompositionContext] = None
165
+ ) -> DecomposeResult:
166
+ pass
167
+
168
+
169
+ def _try_op_decomposer(
170
+ val: Any, decomposer: Optional[OpDecomposer], *, context: Optional[DecompositionContext] = None
171
+ ) -> DecomposeResult:
172
+ if decomposer is None or not isinstance(val, ops.Operation):
173
+ return None
174
+ if 'context' in inspect.signature(decomposer).parameters:
175
+ assert isinstance(decomposer, OpDecomposerWithContext)
176
+ return decomposer(val, context=context)
177
+ else:
178
+ return decomposer(val)
179
+
180
+
181
+ @dataclasses.dataclass(frozen=True)
182
+ class _DecomposeArgs:
183
+ context: Optional[DecompositionContext]
184
+ intercepting_decomposer: Optional[OpDecomposer]
185
+ fallback_decomposer: Optional[OpDecomposer]
186
+ keep: Optional[Callable[['cirq.Operation'], bool]]
187
+ on_stuck_raise: Union[None, Exception, Callable[['cirq.Operation'], Optional[Exception]]]
188
+ preserve_structure: bool
189
+
190
+
191
+ def _decompose_dfs(item: Any, args: _DecomposeArgs) -> Iterator['cirq.Operation']:
192
+ from cirq.circuits import CircuitOperation, FrozenCircuit
193
+
194
+ if isinstance(item, ops.Operation):
195
+ item_untagged = item.untagged
196
+ if args.preserve_structure and isinstance(item_untagged, CircuitOperation):
197
+ new_fc = FrozenCircuit(_decompose_dfs(item_untagged.circuit, args))
198
+ yield item_untagged.replace(circuit=new_fc).with_tags(*item.tags)
199
+ return
200
+ if args.keep is not None and args.keep(item):
201
+ yield item
202
+ return
203
+
204
+ decomposed = _try_op_decomposer(item, args.intercepting_decomposer, context=args.context)
205
+
206
+ if decomposed is NotImplemented or decomposed is None:
207
+ decomposed = decompose_once(item, default=None, flatten=False, context=args.context)
208
+
209
+ if decomposed is NotImplemented or decomposed is None:
210
+ decomposed = _try_op_decomposer(item, args.fallback_decomposer, context=args.context)
211
+
212
+ if decomposed is NotImplemented or decomposed is None:
213
+ if not isinstance(item, ops.Operation) and isinstance(item, Iterable):
214
+ decomposed = item
215
+
216
+ if decomposed is NotImplemented or decomposed is None:
217
+ if args.keep is not None and args.on_stuck_raise is not None:
218
+ if isinstance(args.on_stuck_raise, Exception):
219
+ raise args.on_stuck_raise
220
+ elif callable(args.on_stuck_raise):
221
+ error = args.on_stuck_raise(item)
222
+ if error is not None:
223
+ raise error
224
+ yield item
225
+ else:
226
+ for val in ops.flatten_to_ops(decomposed):
227
+ yield from _decompose_dfs(val, args)
228
+
130
229
 
131
230
  def decompose(
132
231
  val: Any,
@@ -138,6 +237,7 @@ def decompose(
138
237
  None, Exception, Callable[['cirq.Operation'], Optional[Exception]]
139
238
  ] = _value_error_describing_bad_operation,
140
239
  preserve_structure: bool = False,
240
+ context: Optional[DecompositionContext] = None,
141
241
  ) -> List['cirq.Operation']:
142
242
  """Recursively decomposes a value into `cirq.Operation`s meeting a criteria.
143
243
 
@@ -169,6 +269,8 @@ def decompose(
169
269
  preserve_structure: Prevents subcircuits (i.e. `CircuitOperation`s)
170
270
  from being decomposed, but decomposes their contents. If this is
171
271
  True, `intercepting_decomposer` cannot be specified.
272
+ context: Decomposition context specifying common configurable options for
273
+ controlling the behavior of decompose.
172
274
 
173
275
  Returns:
174
276
  A list of operations that the given value was decomposed into. If
@@ -200,55 +302,17 @@ def decompose(
200
302
  "acceptable to keep."
201
303
  )
202
304
 
203
- if preserve_structure:
204
- return _decompose_preserving_structure(
205
- val,
206
- intercepting_decomposer=intercepting_decomposer,
207
- fallback_decomposer=fallback_decomposer,
208
- keep=keep,
209
- on_stuck_raise=on_stuck_raise,
210
- )
211
-
212
- def try_op_decomposer(val: Any, decomposer: Optional[OpDecomposer]) -> DecomposeResult:
213
- if decomposer is None or not isinstance(val, ops.Operation):
214
- return None
215
- return decomposer(val)
216
-
217
- output = []
218
- queue: List[Any] = [val]
219
- while queue:
220
- item = queue.pop(0)
221
- if isinstance(item, ops.Operation) and keep is not None and keep(item):
222
- output.append(item)
223
- continue
224
-
225
- decomposed = try_op_decomposer(item, intercepting_decomposer)
226
-
227
- if decomposed is NotImplemented or decomposed is None:
228
- decomposed = decompose_once(item, default=None)
229
-
230
- if decomposed is NotImplemented or decomposed is None:
231
- decomposed = try_op_decomposer(item, fallback_decomposer)
232
-
233
- if decomposed is not NotImplemented and decomposed is not None:
234
- queue[:0] = ops.flatten_to_ops(decomposed)
235
- continue
236
-
237
- if not isinstance(item, ops.Operation) and isinstance(item, Iterable):
238
- queue[:0] = ops.flatten_to_ops(item)
239
- continue
240
-
241
- if keep is not None and on_stuck_raise is not None:
242
- if isinstance(on_stuck_raise, Exception):
243
- raise on_stuck_raise
244
- elif callable(on_stuck_raise):
245
- error = on_stuck_raise(item)
246
- if error is not None:
247
- raise error
248
-
249
- output.append(item)
250
-
251
- return output
305
+ if context is None:
306
+ context = DecompositionContext(ops.SimpleQubitManager(prefix='_decompose_protocol'))
307
+ args = _DecomposeArgs(
308
+ context=context,
309
+ intercepting_decomposer=intercepting_decomposer,
310
+ fallback_decomposer=fallback_decomposer,
311
+ keep=keep,
312
+ on_stuck_raise=on_stuck_raise,
313
+ preserve_structure=preserve_structure,
314
+ )
315
+ return [*_decompose_dfs(val, args)]
252
316
 
253
317
 
254
318
  # pylint: disable=function-redefined
@@ -261,12 +325,19 @@ def decompose_once(val: Any, **kwargs) -> List['cirq.Operation']:
261
325
 
262
326
  @overload
263
327
  def decompose_once(
264
- val: Any, default: TDefault, *args, **kwargs
328
+ val: Any, default: TDefault, *args, flatten: bool = True, **kwargs
265
329
  ) -> Union[TDefault, List['cirq.Operation']]:
266
330
  pass
267
331
 
268
332
 
269
- def decompose_once(val: Any, default=RaiseTypeErrorIfNotProvided, *args, **kwargs):
333
+ def decompose_once(
334
+ val: Any,
335
+ default=RaiseTypeErrorIfNotProvided,
336
+ *args,
337
+ flatten: bool = True,
338
+ context: Optional[DecompositionContext] = None,
339
+ **kwargs,
340
+ ):
270
341
  """Decomposes a value into operations, if possible.
271
342
 
272
343
  This method decomposes the value exactly once, instead of decomposing it
@@ -282,6 +353,9 @@ def decompose_once(val: Any, default=RaiseTypeErrorIfNotProvided, *args, **kwarg
282
353
  *args: Positional arguments to forward into the `_decompose_` method of
283
354
  `val`. For example, this is used to tell gates what qubits they are
284
355
  being applied to.
356
+ flatten: If True, the returned OP-TREE will be flattened to a list of operations.
357
+ context: Decomposition context specifying common configurable options for
358
+ controlling the behavior of decompose.
285
359
  **kwargs: Keyword arguments to forward into the `_decompose_` method of
286
360
  `val`.
287
361
 
@@ -295,36 +369,62 @@ def decompose_once(val: Any, default=RaiseTypeErrorIfNotProvided, *args, **kwarg
295
369
  TypeError: `val` didn't have a `_decompose_` method (or that method returned
296
370
  `NotImplemented` or `None`) and `default` wasn't set.
297
371
  """
298
- method = getattr(val, '_decompose_', None)
299
- decomposed = NotImplemented if method is None else method(*args, **kwargs)
372
+ if context is None:
373
+ context = DecompositionContext(
374
+ ops.SimpleQubitManager(prefix=f'_decompose_protocol_{next(_CONTEXT_COUNTER)}')
375
+ )
376
+
377
+ method = getattr(val, '_decompose_with_context_', None)
378
+ decomposed = NotImplemented if method is None else method(*args, **kwargs, context=context)
379
+ if decomposed is NotImplemented or None:
380
+ method = getattr(val, '_decompose_', None)
381
+ decomposed = NotImplemented if method is None else method(*args, **kwargs)
300
382
 
301
383
  if decomposed is not NotImplemented and decomposed is not None:
302
- return list(ops.flatten_op_tree(decomposed))
384
+ return list(ops.flatten_to_ops(decomposed)) if flatten else decomposed
303
385
 
304
386
  if default is not RaiseTypeErrorIfNotProvided:
305
387
  return default
306
388
  if method is None:
307
- raise TypeError(f"object of type '{type(val)}' has no _decompose_ method.")
389
+ raise TypeError(
390
+ f"object of type '{type(val)}' has no _decompose_with_context_ or "
391
+ f"_decompose_ method."
392
+ )
308
393
  raise TypeError(
309
- "object of type '{}' does have a _decompose_ method, "
310
- "but it returned NotImplemented or None.".format(type(val))
394
+ f"object of type {type(val)} does have a _decompose_ method, "
395
+ "but it returned NotImplemented or None."
311
396
  )
312
397
 
313
398
 
314
399
  @overload
315
- def decompose_once_with_qubits(val: Any, qubits: Iterable['cirq.Qid']) -> List['cirq.Operation']:
400
+ def decompose_once_with_qubits(
401
+ val: Any,
402
+ qubits: Iterable['cirq.Qid'],
403
+ *,
404
+ flatten: bool = True,
405
+ context: Optional['DecompositionContext'] = None,
406
+ ) -> List['cirq.Operation']:
316
407
  pass
317
408
 
318
409
 
319
410
  @overload
320
411
  def decompose_once_with_qubits(
321
- val: Any, qubits: Iterable['cirq.Qid'], default: Optional[TDefault]
412
+ val: Any,
413
+ qubits: Iterable['cirq.Qid'],
414
+ default: Optional[TDefault],
415
+ *,
416
+ flatten: bool = True,
417
+ context: Optional['DecompositionContext'] = None,
322
418
  ) -> Union[TDefault, List['cirq.Operation']]:
323
419
  pass
324
420
 
325
421
 
326
422
  def decompose_once_with_qubits(
327
- val: Any, qubits: Iterable['cirq.Qid'], default=RaiseTypeErrorIfNotProvided
423
+ val: Any,
424
+ qubits: Iterable['cirq.Qid'],
425
+ default=RaiseTypeErrorIfNotProvided,
426
+ flatten: bool = True,
427
+ context: Optional['DecompositionContext'] = None,
328
428
  ):
329
429
  """Decomposes a value into operations on the given qubits.
330
430
 
@@ -341,6 +441,9 @@ def decompose_once_with_qubits(
341
441
  `_decompose_` method or that method returns `NotImplemented` or
342
442
  `None`. If not specified, non-decomposable values cause a
343
443
  `TypeError`.
444
+ flatten: If True, the returned OP-TREE will be flattened to a list of operations.
445
+ context: Decomposition context specifying common configurable options for
446
+ controlling the behavior of decompose.
344
447
 
345
448
  Returns:
346
449
  The result of `val._decompose_(qubits)`, if `val` has a
@@ -352,7 +455,7 @@ def decompose_once_with_qubits(
352
455
  `val` didn't have a `_decompose_` method (or that method returned
353
456
  `NotImplemented` or `None`) and `default` wasn't set.
354
457
  """
355
- return decompose_once(val, default, tuple(qubits))
458
+ return decompose_once(val, default, tuple(qubits), flatten=flatten, context=context)
356
459
 
357
460
 
358
461
  # pylint: enable=function-redefined
@@ -383,65 +486,4 @@ def _try_decompose_into_operations_and_qubits(
383
486
  qid_shape_dict[q] = max(qid_shape_dict[q], level)
384
487
  qubits = sorted(qubit_set)
385
488
  return result, qubits, tuple(qid_shape_dict[q] for q in qubits)
386
-
387
489
  return None, (), ()
388
-
389
-
390
- def _decompose_preserving_structure(
391
- val: Any,
392
- *,
393
- intercepting_decomposer: Optional[OpDecomposer] = None,
394
- fallback_decomposer: Optional[OpDecomposer] = None,
395
- keep: Optional[Callable[['cirq.Operation'], bool]] = None,
396
- on_stuck_raise: Union[
397
- None, Exception, Callable[['cirq.Operation'], Optional[Exception]]
398
- ] = _value_error_describing_bad_operation,
399
- ) -> List['cirq.Operation']:
400
- """Preserves structure (e.g. subcircuits) while decomposing ops.
401
-
402
- This can be used to reduce a circuit to a particular gateset without
403
- increasing its serialization size. See tests for examples.
404
- """
405
-
406
- # This method provides a generated 'keep' to its decompose() calls.
407
- # If the user-provided keep is not set, on_stuck_raise must be unset to
408
- # ensure that failure to decompose does not generate errors.
409
- on_stuck_raise = on_stuck_raise if keep is not None else None
410
-
411
- from cirq.circuits import CircuitOperation, FrozenCircuit
412
-
413
- visited_fcs = set()
414
-
415
- def keep_structure(op: 'cirq.Operation'):
416
- circuit = getattr(op.untagged, 'circuit', None)
417
- if circuit is not None:
418
- return circuit in visited_fcs
419
- if keep is not None and keep(op):
420
- return True
421
-
422
- def dps_interceptor(op: 'cirq.Operation'):
423
- if not isinstance(op.untagged, CircuitOperation):
424
- if intercepting_decomposer is None:
425
- return NotImplemented
426
- return intercepting_decomposer(op)
427
-
428
- new_fc = FrozenCircuit(
429
- decompose(
430
- op.untagged.circuit,
431
- intercepting_decomposer=dps_interceptor,
432
- fallback_decomposer=fallback_decomposer,
433
- keep=keep_structure,
434
- on_stuck_raise=on_stuck_raise,
435
- )
436
- )
437
- visited_fcs.add(new_fc)
438
- new_co = op.untagged.replace(circuit=new_fc)
439
- return new_co if not op.tags else new_co.with_tags(*op.tags)
440
-
441
- return decompose(
442
- val,
443
- intercepting_decomposer=dps_interceptor,
444
- fallback_decomposer=fallback_decomposer,
445
- keep=keep_structure,
446
- on_stuck_raise=on_stuck_raise,
447
- )
@@ -11,6 +11,9 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
+ import itertools
15
+ from typing import Optional
16
+ from unittest import mock
14
17
  import pytest
15
18
 
16
19
  import cirq
@@ -59,7 +62,7 @@ class DecomposeQuditGate:
59
62
 
60
63
  def test_decompose_once():
61
64
  # No default value results in descriptive error.
62
- with pytest.raises(TypeError, match='no _decompose_ method'):
65
+ with pytest.raises(TypeError, match='no _decompose_with_context_ or _decompose_ method'):
63
66
  _ = cirq.decompose_once(NoMethod())
64
67
  with pytest.raises(TypeError, match='returned NotImplemented or None'):
65
68
  _ = cirq.decompose_once(DecomposeNotImplemented())
@@ -88,7 +91,7 @@ def test_decompose_once_with_qubits():
88
91
  qs = cirq.LineQubit.range(3)
89
92
 
90
93
  # No default value results in descriptive error.
91
- with pytest.raises(TypeError, match='no _decompose_ method'):
94
+ with pytest.raises(TypeError, match='no _decompose_with_context_ or _decompose_ method'):
92
95
  _ = cirq.decompose_once_with_qubits(NoMethod(), qs)
93
96
  with pytest.raises(TypeError, match='returned NotImplemented or None'):
94
97
  _ = cirq.decompose_once_with_qubits(DecomposeNotImplemented(), qs)
@@ -222,6 +225,23 @@ def test_decompose_intercept():
222
225
  )
223
226
  assert actual == [cirq.CNOT(a, b), cirq.CNOT(b, a), cirq.CNOT(a, b)]
224
227
 
228
+ # Accepts a context, when provided.
229
+ def _intercept_with_context(
230
+ op: cirq.Operation, context: Optional[cirq.DecompositionContext] = None
231
+ ):
232
+ assert context is not None
233
+ if op.gate == cirq.SWAP:
234
+ q = context.qubit_manager.qalloc(1)
235
+ a, b = op.qubits
236
+ return [cirq.X(a), cirq.X(*q), cirq.X(b)]
237
+ return NotImplemented
238
+
239
+ context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager())
240
+ actual = cirq.decompose(
241
+ cirq.SWAP(a, b), intercepting_decomposer=_intercept_with_context, context=context
242
+ )
243
+ assert actual == [cirq.X(a), cirq.X(cirq.ops.CleanQubit(0)), cirq.X(b)]
244
+
225
245
 
226
246
  def test_decompose_preserving_structure():
227
247
  a, b = cirq.LineQubit.range(2)
@@ -308,3 +328,113 @@ def test_decompose_tagged_operation():
308
328
  'tag',
309
329
  )
310
330
  assert cirq.decompose_once(op) == cirq.decompose_once(op.untagged)
331
+
332
+
333
+ class RecursiveDecompose(cirq.Gate):
334
+ def __init__(
335
+ self,
336
+ recurse: bool = True,
337
+ mock_qm=mock.Mock(spec=cirq.QubitManager),
338
+ with_context: bool = False,
339
+ ):
340
+ self.recurse = recurse
341
+ self.mock_qm = mock_qm
342
+ self.with_context = with_context
343
+
344
+ def _num_qubits_(self) -> int:
345
+ return 2
346
+
347
+ def _decompose_impl(self, qubits, mock_qm: mock.Mock):
348
+ mock_qm.qalloc(self.recurse)
349
+ yield RecursiveDecompose(
350
+ recurse=False, mock_qm=self.mock_qm, with_context=self.with_context
351
+ ).on(*qubits) if self.recurse else cirq.Z.on_each(*qubits)
352
+ mock_qm.qfree(self.recurse)
353
+
354
+ def _decompose_(self, qubits):
355
+ if self.with_context:
356
+ assert False
357
+ else:
358
+ return self._decompose_impl(qubits, self.mock_qm)
359
+
360
+ def _decompose_with_context_(self, qubits, context):
361
+ if self.with_context:
362
+ qm = self.mock_qm if context is None else context.qubit_manager
363
+ return self._decompose_impl(qubits, qm)
364
+ else:
365
+ return NotImplemented
366
+
367
+ def _has_unitary_(self):
368
+ return True
369
+
370
+
371
+ @pytest.mark.parametrize('with_context', [True, False])
372
+ def test_decompose_recursive_dfs(with_context: bool):
373
+ expected_calls = [
374
+ mock.call.qalloc(True),
375
+ mock.call.qalloc(False),
376
+ mock.call.qfree(False),
377
+ mock.call.qfree(True),
378
+ ]
379
+ mock_qm = mock.Mock(spec=cirq.QubitManager)
380
+ context_qm = mock.Mock(spec=cirq.QubitManager)
381
+ gate = RecursiveDecompose(mock_qm=mock_qm, with_context=with_context)
382
+ q = cirq.LineQubit.range(3)
383
+ gate_op = gate.on(*q[:2])
384
+ tagged_op = gate_op.with_tags("custom tag")
385
+ controlled_op = gate_op.controlled_by(q[2])
386
+ classically_controlled_op = gate_op.with_classical_controls('key')
387
+ moment = cirq.Moment(gate_op)
388
+ circuit = cirq.Circuit(moment)
389
+ for val in [gate_op, tagged_op, controlled_op, classically_controlled_op, moment, circuit]:
390
+ mock_qm.reset_mock()
391
+ _ = cirq.decompose(val, context=cirq.DecompositionContext(qubit_manager=mock_qm))
392
+ assert mock_qm.method_calls == expected_calls
393
+
394
+ mock_qm.reset_mock()
395
+ context_qm.reset_mock()
396
+ _ = cirq.decompose(val, context=cirq.DecompositionContext(context_qm))
397
+ assert (
398
+ context_qm.method_calls == expected_calls
399
+ if with_context
400
+ else mock_qm.method_calls == expected_calls
401
+ )
402
+
403
+
404
+ class G1(cirq.Gate):
405
+ def _num_qubits_(self) -> int:
406
+ return 1
407
+
408
+ def _decompose_with_context_(self, qubits, context):
409
+ yield cirq.CNOT(qubits[0], context.qubit_manager.qalloc(1)[0])
410
+
411
+
412
+ class G2(cirq.Gate):
413
+ def _num_qubits_(self) -> int:
414
+ return 1
415
+
416
+ def _decompose_with_context_(self, qubits, context):
417
+ yield G1()(*context.qubit_manager.qalloc(1))
418
+
419
+
420
+ @mock.patch('cirq.protocols.decompose_protocol._CONTEXT_COUNTER', itertools.count())
421
+ def test_successive_decompose_once_succeed():
422
+ op = G2()(cirq.NamedQubit('q'))
423
+ d1 = cirq.decompose_once(op)
424
+ d2 = cirq.decompose_once(d1[0])
425
+ assert d2 == [
426
+ cirq.CNOT(
427
+ cirq.ops.CleanQubit(0, prefix='_decompose_protocol_0'),
428
+ cirq.ops.CleanQubit(0, prefix='_decompose_protocol_1'),
429
+ )
430
+ ]
431
+
432
+
433
+ def test_decompose_without_context_succeed():
434
+ op = G2()(cirq.NamedQubit('q'))
435
+ assert cirq.decompose(op, keep=lambda op: op.gate is cirq.CNOT) == [
436
+ cirq.CNOT(
437
+ cirq.ops.CleanQubit(0, prefix='_decompose_protocol'),
438
+ cirq.ops.CleanQubit(1, prefix='_decompose_protocol'),
439
+ )
440
+ ]
@@ -63,7 +63,8 @@ def _strat_has_stabilizer_effect_from_unitary(val: Any) -> Optional[bool]:
63
63
  """
64
64
  # Do not try this strategy if there is no unitary or if the number of
65
65
  # qubits is not 1 since that would be expensive.
66
- if not protocols.has_unitary(val) or protocols.num_qubits(val) != 1:
66
+ qid_shape = protocols.qid_shape(val, default=None)
67
+ if qid_shape is None or len(qid_shape) != 1 or not protocols.has_unitary(val):
67
68
  return None
68
69
  unitary = protocols.unitary(val)
69
70
  return SingleQubitCliffordGate.from_unitary(unitary) is not None
@@ -115,9 +115,9 @@ def inverse(val: Any, default: Any = RaiseTypeErrorIfNotProvided) -> Any:
115
115
  if default is not RaiseTypeErrorIfNotProvided:
116
116
  return default
117
117
  raise TypeError(
118
- "object of type '{}' isn't invertible. "
118
+ f"object of type '{type(val)}' isn't invertible. "
119
119
  "It has no __pow__ method (or the method returned NotImplemented) "
120
- "and it isn't an iterable of invertible objects.".format(type(val))
120
+ "and it isn't an iterable of invertible objects."
121
121
  )
122
122
 
123
123
 
@@ -57,8 +57,8 @@ TESTED_MODULES: Dict[str, Optional[_ModuleDeprecation]] = {
57
57
 
58
58
 
59
59
  # pyQuil 3.0, necessary for cirq_rigetti module requires
60
- # python >= 3.7
61
- if sys.version_info < (3, 7): # pragma: no cover
60
+ # python >= 3.9
61
+ if sys.version_info < (3, 9): # pragma: no cover
62
62
  del TESTED_MODULES['cirq_rigetti']
63
63
 
64
64
 
@@ -767,7 +767,7 @@ def test_pathlib_paths(tmpdir):
767
767
  assert cirq.read_json_gzip(gzip_path) == cirq.X
768
768
 
769
769
 
770
- def test_dataclass_json_dict():
770
+ def test_dataclass_json_dict() -> None:
771
771
  @dataclasses.dataclass(frozen=True)
772
772
  class MyDC:
773
773
  q: cirq.LineQubit
@@ -1,7 +1,20 @@
1
- {
2
- "cirq_type": "Linspace",
3
- "key": "a",
4
- "start": 0,
5
- "stop": 1,
6
- "length": 4
7
- }
1
+ [
2
+ {
3
+ "cirq_type": "Linspace",
4
+ "key": "a",
5
+ "start": 0,
6
+ "stop": 1,
7
+ "length": 4
8
+ },
9
+ {
10
+ "cirq_type": "Linspace",
11
+ "key": "b",
12
+ "start": 1,
13
+ "stop": 2,
14
+ "length": 3,
15
+ "metadata": {
16
+ "cirq_type": "NamedQubit",
17
+ "name": "b"
18
+ }
19
+ }
20
+ ]
@@ -1 +1,4 @@
1
- cirq.Linspace("a", start=0, stop=1, length=4)
1
+ [
2
+ cirq.Linspace("a", start=0, stop=1, length=4),
3
+ cirq.Linspace("b", start=1, stop=2, length=3, metadata=cirq.NamedQubit("b"))
4
+ ]
@@ -1,8 +1,19 @@
1
- {
2
- "cirq_type": "Points",
3
- "key": "a",
4
- "points": [
5
- 0,
6
- 0.4
7
- ]
8
- }
1
+ [
2
+ {
3
+ "cirq_type": "Points",
4
+ "key": "a",
5
+ "points": [
6
+ 0,
7
+ 0.4
8
+ ]
9
+ },
10
+ {
11
+ "cirq_type": "Points",
12
+ "key": "amp",
13
+ "points": [
14
+ 0,
15
+ 1
16
+ ],
17
+ "metadata": "used to turn amp on or off"
18
+ }
19
+ ]