cirq-core 1.7.0.dev20250924231107__py3-none-any.whl → 1.7.0.dev20251203004401__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 (392) hide show
  1. cirq/__init__.py +1 -0
  2. cirq/_compat.py +3 -2
  3. cirq/_compat_test.py +16 -15
  4. cirq/_doc.py +4 -3
  5. cirq/_import.py +2 -1
  6. cirq/_version.py +1 -1
  7. cirq/_version_test.py +1 -1
  8. cirq/circuits/_bucket_priority_queue.py +2 -1
  9. cirq/circuits/circuit.py +7 -13
  10. cirq/circuits/circuit_operation.py +2 -1
  11. cirq/circuits/circuit_test.py +13 -12
  12. cirq/circuits/frozen_circuit.py +3 -2
  13. cirq/circuits/moment.py +3 -15
  14. cirq/circuits/optimization_pass.py +2 -1
  15. cirq/circuits/qasm_output.py +39 -10
  16. cirq/circuits/qasm_output_test.py +51 -2
  17. cirq/circuits/text_diagram_drawer.py +2 -1
  18. cirq/contrib/acquaintance/bipartite.py +2 -1
  19. cirq/contrib/acquaintance/devices.py +1 -1
  20. cirq/contrib/acquaintance/executor.py +4 -5
  21. cirq/contrib/acquaintance/executor_test.py +2 -1
  22. cirq/contrib/acquaintance/gates.py +2 -1
  23. cirq/contrib/acquaintance/gates_test.py +1 -1
  24. cirq/contrib/acquaintance/inspection_utils.py +2 -1
  25. cirq/contrib/acquaintance/mutation_utils.py +2 -1
  26. cirq/contrib/acquaintance/optimizers.py +2 -1
  27. cirq/contrib/acquaintance/permutation.py +2 -1
  28. cirq/contrib/acquaintance/permutation_test.py +1 -1
  29. cirq/contrib/acquaintance/shift.py +2 -1
  30. cirq/contrib/acquaintance/shift_swap_network.py +2 -1
  31. cirq/contrib/acquaintance/strategies/complete.py +3 -2
  32. cirq/contrib/acquaintance/strategies/cubic.py +2 -1
  33. cirq/contrib/acquaintance/strategies/quartic_paired.py +2 -1
  34. cirq/contrib/acquaintance/strategies/quartic_paired_test.py +1 -1
  35. cirq/contrib/acquaintance/testing.py +2 -1
  36. cirq/contrib/acquaintance/topological_sort.py +2 -1
  37. cirq/contrib/bayesian_network/bayesian_network_gate.py +3 -2
  38. cirq/contrib/circuitdag/circuit_dag.py +4 -2
  39. cirq/contrib/custom_simulators/custom_state_simulator.py +2 -1
  40. cirq/contrib/custom_simulators/custom_state_simulator_test.py +1 -1
  41. cirq/contrib/graph_device/graph_device.py +2 -1
  42. cirq/contrib/graph_device/graph_device_test.py +2 -1
  43. cirq/contrib/graph_device/hypergraph.py +2 -1
  44. cirq/contrib/graph_device/uniform_graph_device.py +2 -1
  45. cirq/contrib/json.py +14 -2
  46. cirq/contrib/json_test_data/BayesianNetworkGate.json +10 -0
  47. cirq/contrib/json_test_data/BayesianNetworkGate.repr +3 -0
  48. cirq/contrib/json_test_data/QuantumVolumeResult.json +169 -0
  49. cirq/contrib/json_test_data/QuantumVolumeResult.repr +22 -0
  50. cirq/contrib/json_test_data/SwapPermutationGate.json +3 -0
  51. cirq/contrib/json_test_data/SwapPermutationGate.repr +1 -0
  52. cirq/contrib/json_test_data/spec.py +0 -2
  53. cirq/contrib/noise_models/noise_models.py +2 -1
  54. cirq/contrib/paulistring/clifford_optimize.py +20 -2
  55. cirq/contrib/paulistring/optimize.py +1 -1
  56. cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation.py +146 -35
  57. cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation_test.py +74 -171
  58. cirq/contrib/paulistring/recombine.py +5 -2
  59. cirq/contrib/paulistring/separate.py +1 -1
  60. cirq/contrib/qasm_import/_parser.py +2 -1
  61. cirq/contrib/qasm_import/_parser_test.py +3 -3
  62. cirq/contrib/quantikz/__init__.py +21 -0
  63. cirq/contrib/quantikz/circuit_to_latex_quantikz.py +680 -0
  64. cirq/contrib/quantikz/circuit_to_latex_quantikz_test.py +253 -0
  65. cirq/contrib/quantikz/circuit_to_latex_render.py +424 -0
  66. cirq/contrib/quantikz/circuit_to_latex_render_test.py +44 -0
  67. cirq/contrib/quantum_volume/quantum_volume.py +2 -1
  68. cirq/contrib/quimb/density_matrix.py +1 -1
  69. cirq/contrib/quimb/grid_circuits.py +2 -1
  70. cirq/contrib/quimb/grid_circuits_test.py +1 -1
  71. cirq/contrib/quimb/mps_simulator.py +4 -3
  72. cirq/contrib/quimb/state_vector.py +2 -1
  73. cirq/contrib/quirk/export_to_quirk.py +2 -1
  74. cirq/contrib/quirk/linearize_circuit.py +1 -1
  75. cirq/contrib/quirk/quirk_gate.py +2 -1
  76. cirq/contrib/routing/device.py +1 -1
  77. cirq/contrib/routing/greedy.py +2 -1
  78. cirq/contrib/routing/initialization.py +2 -1
  79. cirq/contrib/routing/router.py +2 -1
  80. cirq/contrib/routing/swap_network.py +2 -1
  81. cirq/contrib/routing/utils.py +2 -1
  82. cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py +7 -5
  83. cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py +6 -6
  84. cirq/devices/device.py +2 -1
  85. cirq/devices/grid_device_metadata.py +2 -1
  86. cirq/devices/grid_qubit.py +7 -6
  87. cirq/devices/insertion_noise_model.py +2 -1
  88. cirq/devices/line_qubit.py +2 -1
  89. cirq/devices/named_topologies.py +2 -1
  90. cirq/devices/noise_model.py +2 -1
  91. cirq/devices/noise_model_test.py +1 -1
  92. cirq/devices/noise_properties.py +2 -1
  93. cirq/devices/superconducting_qubits_noise_properties_test.py +2 -1
  94. cirq/devices/thermal_noise_model.py +2 -1
  95. cirq/experiments/__init__.py +2 -0
  96. cirq/experiments/benchmarking/parallel_xeb.py +2 -1
  97. cirq/experiments/benchmarking/parallel_xeb_test.py +1 -1
  98. cirq/experiments/fidelity_estimation.py +2 -1
  99. cirq/experiments/fidelity_estimation_test.py +1 -1
  100. cirq/experiments/ghz_2d.py +150 -0
  101. cirq/experiments/ghz_2d_test.py +155 -0
  102. cirq/experiments/n_qubit_tomography.py +2 -1
  103. cirq/experiments/n_qubit_tomography_test.py +1 -1
  104. cirq/experiments/purity_estimation.py +1 -1
  105. cirq/experiments/qubit_characterizations.py +2 -1
  106. cirq/experiments/random_quantum_circuit_generation.py +2 -1
  107. cirq/experiments/random_quantum_circuit_generation_test.py +2 -1
  108. cirq/experiments/readout_confusion_matrix.py +2 -1
  109. cirq/experiments/readout_confusion_matrix_test.py +1 -1
  110. cirq/experiments/single_qubit_readout_calibration.py +2 -1
  111. cirq/experiments/single_qubit_readout_calibration_test.py +1 -1
  112. cirq/experiments/t1_decay_experiment.py +2 -1
  113. cirq/experiments/two_qubit_xeb.py +2 -1
  114. cirq/experiments/two_qubit_xeb_test.py +1 -1
  115. cirq/experiments/xeb_fitting.py +2 -1
  116. cirq/experiments/xeb_fitting_test.py +1 -1
  117. cirq/experiments/xeb_sampling.py +5 -3
  118. cirq/experiments/xeb_sampling_test.py +1 -1
  119. cirq/experiments/xeb_simulation.py +2 -1
  120. cirq/experiments/xeb_simulation_test.py +2 -1
  121. cirq/experiments/z_phase_calibration.py +2 -1
  122. cirq/experiments/z_phase_calibration_test.py +18 -3
  123. cirq/interop/quirk/cells/__init__.py +1 -2
  124. cirq/interop/quirk/cells/all_cells.py +2 -1
  125. cirq/interop/quirk/cells/arithmetic_cells.py +2 -1
  126. cirq/interop/quirk/cells/cell.py +2 -1
  127. cirq/interop/quirk/cells/composite_cell.py +2 -1
  128. cirq/interop/quirk/cells/composite_cell_test.py +1 -1
  129. cirq/interop/quirk/cells/control_cells.py +2 -1
  130. cirq/interop/quirk/cells/frequency_space_cells.py +1 -1
  131. cirq/interop/quirk/cells/ignored_cells.py +1 -1
  132. cirq/interop/quirk/cells/input_cells.py +2 -1
  133. cirq/interop/quirk/cells/input_rotation_cells.py +2 -1
  134. cirq/interop/quirk/cells/measurement_cells.py +2 -1
  135. cirq/interop/quirk/cells/parse.py +2 -11
  136. cirq/interop/quirk/cells/qubit_permutation_cells.py +2 -1
  137. cirq/interop/quirk/cells/scalar_cells.py +2 -1
  138. cirq/interop/quirk/cells/single_qubit_rotation_cells.py +2 -1
  139. cirq/interop/quirk/cells/swap_cell.py +2 -1
  140. cirq/interop/quirk/cells/unsupported_cells.py +1 -1
  141. cirq/interop/quirk/url_to_circuit.py +2 -1
  142. cirq/json_resolver_cache.py +0 -2
  143. cirq/linalg/decompositions.py +6 -2
  144. cirq/linalg/decompositions_test.py +1 -0
  145. cirq/linalg/diagonalize.py +1 -1
  146. cirq/linalg/predicates.py +2 -1
  147. cirq/linalg/tolerance.py +2 -1
  148. cirq/linalg/transformations.py +2 -1
  149. cirq/ops/arithmetic_operation.py +2 -1
  150. cirq/ops/arithmetic_operation_test.py +1 -1
  151. cirq/ops/boolean_hamiltonian.py +4 -3
  152. cirq/ops/classically_controlled_operation.py +3 -2
  153. cirq/ops/clifford_gate.py +2 -1
  154. cirq/ops/clifford_gate_test.py +1 -2
  155. cirq/ops/common_channels.py +2 -1
  156. cirq/ops/common_gates.py +2 -1
  157. cirq/ops/control_values.py +2 -1
  158. cirq/ops/controlled_gate.py +3 -2
  159. cirq/ops/controlled_gate_test.py +2 -1
  160. cirq/ops/controlled_operation.py +3 -2
  161. cirq/ops/controlled_operation_test.py +2 -1
  162. cirq/ops/dense_pauli_string.py +3 -13
  163. cirq/ops/diagonal_gate.py +3 -2
  164. cirq/ops/eigen_gate.py +9 -7
  165. cirq/ops/fourier_transform.py +3 -2
  166. cirq/ops/fourier_transform_test.py +2 -4
  167. cirq/ops/fsim_gate.py +3 -2
  168. cirq/ops/gate_operation.py +8 -12
  169. cirq/ops/gateset.py +22 -2
  170. cirq/ops/global_phase_op.py +3 -2
  171. cirq/ops/greedy_qubit_manager.py +2 -1
  172. cirq/ops/identity.py +2 -1
  173. cirq/ops/kraus_channel.py +2 -1
  174. cirq/ops/linear_combinations.py +8 -3
  175. cirq/ops/linear_combinations_test.py +23 -1
  176. cirq/ops/matrix_gates.py +2 -1
  177. cirq/ops/measure_util.py +2 -1
  178. cirq/ops/measurement_gate.py +2 -1
  179. cirq/ops/mixed_unitary_channel.py +2 -1
  180. cirq/ops/named_qubit.py +2 -2
  181. cirq/ops/op_tree.py +2 -1
  182. cirq/ops/parallel_gate.py +3 -2
  183. cirq/ops/parity_gates.py +2 -1
  184. cirq/ops/pauli_interaction_gate.py +2 -1
  185. cirq/ops/pauli_measurement_gate.py +2 -1
  186. cirq/ops/pauli_string.py +6 -12
  187. cirq/ops/pauli_string_phasor.py +3 -2
  188. cirq/ops/pauli_string_raw_types.py +2 -1
  189. cirq/ops/pauli_string_test.py +2 -2
  190. cirq/ops/pauli_sum_exponential.py +2 -1
  191. cirq/ops/permutation_gate.py +2 -1
  192. cirq/ops/phased_iswap_gate.py +3 -2
  193. cirq/ops/phased_x_gate.py +5 -4
  194. cirq/ops/phased_x_z_gate.py +12 -5
  195. cirq/ops/projector.py +2 -1
  196. cirq/ops/qubit_manager.py +2 -1
  197. cirq/ops/qubit_order.py +2 -1
  198. cirq/ops/qubit_order_or_list.py +1 -1
  199. cirq/ops/random_gate_channel.py +3 -2
  200. cirq/ops/raw_types.py +7 -15
  201. cirq/ops/raw_types_test.py +4 -3
  202. cirq/ops/state_preparation_channel.py +2 -1
  203. cirq/ops/three_qubit_gates.py +3 -2
  204. cirq/ops/two_qubit_diagonal_gate.py +3 -2
  205. cirq/ops/uniform_superposition_gate.py +2 -1
  206. cirq/ops/wait_gate.py +10 -4
  207. cirq/protocols/act_on_protocol.py +2 -1
  208. cirq/protocols/act_on_protocol_test.py +2 -1
  209. cirq/protocols/apply_channel_protocol.py +2 -1
  210. cirq/protocols/apply_mixture_protocol.py +2 -1
  211. cirq/protocols/apply_mixture_protocol_test.py +2 -1
  212. cirq/protocols/apply_unitary_protocol.py +2 -1
  213. cirq/protocols/apply_unitary_protocol_test.py +2 -0
  214. cirq/protocols/approximate_equality_protocol.py +2 -1
  215. cirq/protocols/circuit_diagram_info_protocol.py +2 -1
  216. cirq/protocols/decompose_protocol.py +2 -12
  217. cirq/protocols/has_stabilizer_effect_protocol.py +1 -1
  218. cirq/protocols/hash_from_pickle_test.py +2 -2
  219. cirq/protocols/inverse_protocol.py +2 -1
  220. cirq/protocols/json_serialization.py +2 -1
  221. cirq/protocols/kraus_protocol.py +4 -3
  222. cirq/protocols/kraus_protocol_test.py +2 -2
  223. cirq/protocols/measurement_key_protocol.py +2 -1
  224. cirq/protocols/mixture_protocol.py +2 -1
  225. cirq/protocols/pow_protocol.py +2 -1
  226. cirq/protocols/qasm.py +2 -1
  227. cirq/protocols/qid_shape_protocol.py +2 -1
  228. cirq/protocols/resolve_parameters.py +16 -14
  229. cirq/protocols/trace_distance_bound.py +2 -1
  230. cirq/protocols/unitary_protocol.py +21 -21
  231. cirq/qis/channels.py +1 -1
  232. cirq/qis/channels_test.py +1 -1
  233. cirq/qis/clifford_tableau.py +2 -1
  234. cirq/qis/entropy.py +2 -2
  235. cirq/qis/quantum_state_representation.py +2 -1
  236. cirq/qis/states.py +7 -2
  237. cirq/sim/classical_simulator.py +2 -1
  238. cirq/sim/clifford/clifford_simulator.py +2 -1
  239. cirq/sim/clifford/clifford_simulator_test.py +1 -1
  240. cirq/sim/clifford/clifford_tableau_simulation_state.py +2 -1
  241. cirq/sim/clifford/stabilizer_ch_form_simulation_state.py +2 -1
  242. cirq/sim/clifford/stabilizer_sampler.py +1 -1
  243. cirq/sim/clifford/stabilizer_simulation_state.py +2 -1
  244. cirq/sim/clifford/stabilizer_state_ch_form.py +3 -2
  245. cirq/sim/clifford/stabilizer_state_ch_form_test.py +0 -1
  246. cirq/sim/density_matrix_simulation_state.py +2 -1
  247. cirq/sim/density_matrix_simulator.py +2 -1
  248. cirq/sim/density_matrix_utils.py +2 -1
  249. cirq/sim/mux.py +2 -1
  250. cirq/sim/mux_test.py +0 -1
  251. cirq/sim/simulation_product_state.py +2 -1
  252. cirq/sim/simulation_product_state_test.py +2 -1
  253. cirq/sim/simulation_state.py +2 -1
  254. cirq/sim/simulation_state_base.py +2 -1
  255. cirq/sim/simulation_state_test.py +2 -1
  256. cirq/sim/simulation_utils.py +2 -1
  257. cirq/sim/simulator.py +2 -1
  258. cirq/sim/simulator_base.py +2 -1
  259. cirq/sim/simulator_base_test.py +2 -1
  260. cirq/sim/simulator_test.py +2 -1
  261. cirq/sim/sparse_simulator.py +2 -1
  262. cirq/sim/state_vector.py +2 -1
  263. cirq/sim/state_vector_simulation_state.py +2 -1
  264. cirq/sim/state_vector_simulator.py +2 -1
  265. cirq/sim/state_vector_test.py +1 -2
  266. cirq/study/__init__.py +1 -0
  267. cirq/study/flatten_expressions.py +2 -1
  268. cirq/study/resolver.py +31 -18
  269. cirq/study/resolver_test.py +1 -1
  270. cirq/study/result.py +2 -1
  271. cirq/study/sweepable.py +2 -1
  272. cirq/study/sweeps.py +26 -1
  273. cirq/study/sweeps_test.py +24 -0
  274. cirq/testing/_compat_test_data/__init__.py +3 -3
  275. cirq/testing/circuit_compare.py +2 -1
  276. cirq/testing/consistent_act_on_test.py +1 -1
  277. cirq/testing/consistent_controlled_gate_op.py +1 -1
  278. cirq/testing/consistent_controlled_gate_op_test.py +2 -1
  279. cirq/testing/consistent_protocols.py +2 -1
  280. cirq/testing/consistent_protocols_test.py +3 -3
  281. cirq/testing/consistent_qasm.py +2 -1
  282. cirq/testing/consistent_resolve_parameters.py +1 -1
  283. cirq/testing/consistent_unitary.py +1 -1
  284. cirq/testing/consistent_unitary_test.py +1 -1
  285. cirq/testing/deprecation.py +1 -1
  286. cirq/testing/devices.py +3 -2
  287. cirq/testing/equals_tester.py +4 -3
  288. cirq/testing/equivalent_basis_map.py +4 -2
  289. cirq/testing/json.py +3 -2
  290. cirq/testing/logs.py +1 -1
  291. cirq/testing/op_tree.py +1 -1
  292. cirq/testing/order_tester.py +2 -2
  293. cirq/testing/pytest_utils.py +2 -1
  294. cirq/testing/random_circuit.py +2 -1
  295. cirq/testing/random_circuit_test.py +2 -1
  296. cirq/testing/repr_pretty_tester.py +3 -3
  297. cirq/transformers/__init__.py +1 -0
  298. cirq/transformers/_connected_component.py +231 -0
  299. cirq/transformers/_connected_component_test.py +200 -0
  300. cirq/transformers/align_test.py +13 -13
  301. cirq/transformers/analytical_decompositions/clifford_decomposition.py +8 -7
  302. cirq/transformers/analytical_decompositions/clifford_decomposition_test.py +5 -5
  303. cirq/transformers/analytical_decompositions/controlled_gate_decomposition.py +11 -10
  304. cirq/transformers/analytical_decompositions/controlled_gate_decomposition_test.py +6 -6
  305. cirq/transformers/analytical_decompositions/cphase_to_fsim.py +3 -2
  306. cirq/transformers/analytical_decompositions/cphase_to_fsim_test.py +11 -10
  307. cirq/transformers/analytical_decompositions/quantum_shannon_decomposition.py +8 -7
  308. cirq/transformers/analytical_decompositions/quantum_shannon_decomposition_test.py +17 -20
  309. cirq/transformers/analytical_decompositions/single_qubit_decompositions_test.py +33 -27
  310. cirq/transformers/analytical_decompositions/single_to_two_qubit_isometry_test.py +1 -1
  311. cirq/transformers/analytical_decompositions/three_qubit_decomposition.py +1 -1
  312. cirq/transformers/analytical_decompositions/two_qubit_state_preparation_test.py +12 -11
  313. cirq/transformers/analytical_decompositions/two_qubit_to_cz.py +5 -2
  314. cirq/transformers/analytical_decompositions/two_qubit_to_cz_test.py +3 -3
  315. cirq/transformers/analytical_decompositions/two_qubit_to_fsim.py +2 -1
  316. cirq/transformers/analytical_decompositions/two_qubit_to_ms.py +2 -1
  317. cirq/transformers/analytical_decompositions/two_qubit_to_ms_test.py +2 -2
  318. cirq/transformers/analytical_decompositions/two_qubit_to_sqrt_iswap.py +2 -1
  319. cirq/transformers/analytical_decompositions/two_qubit_to_sqrt_iswap_test.py +32 -30
  320. cirq/transformers/drop_negligible_operations_test.py +7 -7
  321. cirq/transformers/dynamical_decoupling.py +185 -112
  322. cirq/transformers/dynamical_decoupling_test.py +195 -201
  323. cirq/transformers/eject_phased_paulis.py +2 -1
  324. cirq/transformers/eject_phased_paulis_test.py +3 -2
  325. cirq/transformers/eject_z.py +4 -3
  326. cirq/transformers/eject_z_test.py +23 -25
  327. cirq/transformers/expand_composite.py +3 -2
  328. cirq/transformers/expand_composite_test.py +14 -14
  329. cirq/transformers/gauge_compiling/__init__.py +8 -0
  330. cirq/transformers/gauge_compiling/gauge_compiling.py +3 -2
  331. cirq/transformers/gauge_compiling/gauge_compiling_test.py +14 -12
  332. cirq/transformers/gauge_compiling/gauge_compiling_test_utils.py +3 -3
  333. cirq/transformers/gauge_compiling/idle_moments_gauge.py +5 -2
  334. cirq/transformers/gauge_compiling/multi_moment_cphase_gauge.py +242 -0
  335. cirq/transformers/gauge_compiling/multi_moment_cphase_gauge_test.py +243 -0
  336. cirq/transformers/gauge_compiling/multi_moment_gauge_compiling.py +151 -0
  337. cirq/transformers/gauge_compiling/sqrt_cz_gauge.py +2 -1
  338. cirq/transformers/heuristic_decompositions/gate_tabulation_math_utils.py +1 -1
  339. cirq/transformers/heuristic_decompositions/gate_tabulation_math_utils_test.py +6 -6
  340. cirq/transformers/heuristic_decompositions/two_qubit_gate_tabulation.py +2 -1
  341. cirq/transformers/measurement_transformers.py +2 -1
  342. cirq/transformers/measurement_transformers_test.py +45 -39
  343. cirq/transformers/merge_k_qubit_gates.py +2 -1
  344. cirq/transformers/merge_k_qubit_gates_test.py +1 -1
  345. cirq/transformers/merge_single_qubit_gates.py +2 -1
  346. cirq/transformers/merge_single_qubit_gates_test.py +22 -22
  347. cirq/transformers/noise_adding_test.py +2 -2
  348. cirq/transformers/optimize_for_target_gateset.py +2 -1
  349. cirq/transformers/optimize_for_target_gateset_test.py +11 -9
  350. cirq/transformers/qubit_management_transformers_test.py +6 -2
  351. cirq/transformers/routing/mapping_manager.py +2 -1
  352. cirq/transformers/routing/route_circuit_cqc.py +2 -1
  353. cirq/transformers/stratify.py +2 -1
  354. cirq/transformers/symbolize.py +2 -1
  355. cirq/transformers/tag_transformers.py +2 -1
  356. cirq/transformers/target_gatesets/compilation_target_gateset.py +2 -1
  357. cirq/transformers/target_gatesets/cz_gateset.py +2 -1
  358. cirq/transformers/target_gatesets/cz_gateset_test.py +1 -1
  359. cirq/transformers/target_gatesets/sqrt_iswap_gateset.py +2 -1
  360. cirq/transformers/transformer_api.py +2 -1
  361. cirq/transformers/transformer_primitives.py +271 -145
  362. cirq/transformers/transformer_primitives_test.py +185 -1
  363. cirq/value/abc_alt.py +2 -1
  364. cirq/value/classical_data.py +2 -1
  365. cirq/value/condition.py +2 -1
  366. cirq/value/digits.py +9 -2
  367. cirq/value/duration.py +6 -5
  368. cirq/value/linear_dict.py +4 -9
  369. cirq/value/measurement_key.py +2 -1
  370. cirq/value/periodic_value.py +3 -2
  371. cirq/value/product_state.py +2 -1
  372. cirq/value/value_equality_attr.py +2 -1
  373. cirq/vis/density_matrix.py +1 -1
  374. cirq/vis/heatmap.py +2 -1
  375. cirq/vis/histogram.py +2 -1
  376. cirq/vis/state_histogram.py +2 -1
  377. cirq/work/collector.py +2 -1
  378. cirq/work/observable_grouping.py +2 -1
  379. cirq/work/observable_measurement.py +2 -1
  380. cirq/work/observable_measurement_data.py +2 -1
  381. cirq/work/observable_measurement_test.py +1 -1
  382. cirq/work/observable_readout_calibration.py +2 -1
  383. cirq/work/observable_readout_calibration_test.py +1 -1
  384. cirq/work/observable_settings.py +2 -1
  385. cirq/work/sampler.py +2 -1
  386. cirq/work/sampler_test.py +1 -1
  387. {cirq_core-1.7.0.dev20250924231107.dist-info → cirq_core-1.7.0.dev20251203004401.dist-info}/METADATA +4 -4
  388. {cirq_core-1.7.0.dev20250924231107.dist-info → cirq_core-1.7.0.dev20251203004401.dist-info}/RECORD +391 -374
  389. cirq/contrib/json_test.py +0 -33
  390. {cirq_core-1.7.0.dev20250924231107.dist-info → cirq_core-1.7.0.dev20251203004401.dist-info}/WHEEL +0 -0
  391. {cirq_core-1.7.0.dev20250924231107.dist-info → cirq_core-1.7.0.dev20251203004401.dist-info}/licenses/LICENSE +0 -0
  392. {cirq_core-1.7.0.dev20250924231107.dist-info → cirq_core-1.7.0.dev20251203004401.dist-info}/top_level.txt +0 -0
@@ -17,12 +17,22 @@
17
17
  from __future__ import annotations
18
18
 
19
19
  import bisect
20
+ import copy
20
21
  import dataclasses
22
+ import itertools
21
23
  from collections import defaultdict
22
- from typing import Callable, cast, Hashable, Sequence, TYPE_CHECKING
24
+ from collections.abc import Callable, Hashable, Sequence
25
+ from typing import cast, TYPE_CHECKING
23
26
 
24
27
  from cirq import circuits, ops, protocols
25
28
  from cirq.circuits.circuit import CIRCUIT_TYPE
29
+ from cirq.transformers._connected_component import (
30
+ Component,
31
+ ComponentSet,
32
+ ComponentWithCircuitOp,
33
+ ComponentWithCircuitOpSet,
34
+ ComponentWithOpsSet,
35
+ )
26
36
 
27
37
  if TYPE_CHECKING:
28
38
  import cirq
@@ -49,6 +59,33 @@ def _create_target_circuit_type(ops: ops.OP_TREE, target_circuit: CIRCUIT_TYPE)
49
59
  return cast(CIRCUIT_TYPE, circuits.Circuit(ops))
50
60
 
51
61
 
62
+ def _insort_last(indices: list[int], value: int) -> None:
63
+ """Insert value to a sorted list of indices.
64
+
65
+ Optimized for the majority case when the value is the last in the list.
66
+ """
67
+ if indices and value < indices[-1]:
68
+ bisect.insort(indices, value)
69
+ else:
70
+ indices.append(value)
71
+
72
+
73
+ def _remove_last(indices: list[int], value: int) -> None:
74
+ """Remove value from a sorted list of indices.
75
+
76
+ Optimized for the majority case when the value is the last in the list.
77
+
78
+ Raises:
79
+ ValueError: If the value is not in the list.
80
+ """
81
+ if indices and (
82
+ indices[pos := -1] == value or indices[pos := bisect.bisect_left(indices, value)] == value
83
+ ):
84
+ indices.pop(pos)
85
+ else:
86
+ raise ValueError("The value is not in the list of indices") # pragma: no cover
87
+
88
+
52
89
  def map_moments(
53
90
  circuit: CIRCUIT_TYPE,
54
91
  map_func: Callable[[circuits.Moment, int], circuits.Moment | Sequence[circuits.Moment]],
@@ -282,17 +319,23 @@ def map_operations_and_unroll(
282
319
 
283
320
  @dataclasses.dataclass
284
321
  class _MergedCircuit:
285
- """An optimized internal representation of a circuit, tailored for `cirq.merge_operations`
322
+ """An optimized internal representation of a circuit, tailored for merge operations.
323
+
324
+ Operations are represented as mergeable components.
325
+ Each component has a moment id, a set of qubits, a set of measurement keys, and a set of
326
+ control keys. The moment id is the index of the moment that contains the component.
286
327
 
287
328
  Attributes:
288
- qubit_indexes: Mapping from qubits to (sorted) list of moment indexes containing operations
289
- acting on the qubit.
290
- mkey_indexes: Mapping from measurement keys to (sorted) list of moment indexes containing
291
- measurement operations with the same key.
292
- ckey_indexes: Mapping from measurement keys to (sorted) list of moment indexes containing
293
- classically controlled operations controlled on the same key.
294
- ops_by_index: List of circuit moments containing operations. We use a dictionary instead
295
- of a set to store operations to preserve insertion order.
329
+ qubit_indexes: Mapping from qubits to (sorted) list of component moments containing
330
+ operations acting on the qubit.
331
+ mkey_indexes: Mapping from measurement keys to (sorted) list of component moments
332
+ containing measurement operations with the same key.
333
+ ckey_indexes: Mapping from measurement keys to (sorted) list of component moments
334
+ containing classically controlled operations controlled on the same key.
335
+ components_by_index: List of components indexed by moment id.
336
+ For a moment id, we use a dictionary to keep track of the
337
+ components in the moment. The dictionary instead of a set is used to preserve
338
+ insertion order and the dictionary's values are intentionally unused.
296
339
  """
297
340
 
298
341
  qubit_indexes: dict[cirq.Qid, list[int]] = dataclasses.field(
@@ -304,54 +347,203 @@ class _MergedCircuit:
304
347
  ckey_indexes: dict[cirq.MeasurementKey, list[int]] = dataclasses.field(
305
348
  default_factory=lambda: defaultdict(lambda: [-1])
306
349
  )
307
- ops_by_index: list[dict[cirq.Operation, int]] = dataclasses.field(default_factory=list)
350
+ components_by_index: list[dict[Component, None]] = dataclasses.field(default_factory=list)
308
351
 
309
352
  def append_empty_moment(self) -> None:
310
- self.ops_by_index.append({})
311
-
312
- def add_op_to_moment(self, moment_index: int, op: cirq.Operation) -> None:
313
- self.ops_by_index[moment_index][op] = 0
314
- for q in op.qubits:
315
- if moment_index > self.qubit_indexes[q][-1]:
316
- self.qubit_indexes[q].append(moment_index)
317
- else:
318
- bisect.insort(self.qubit_indexes[q], moment_index)
319
- for mkey in protocols.measurement_key_objs(op):
320
- bisect.insort(self.mkey_indexes[mkey], moment_index)
321
- for ckey in protocols.control_keys(op):
322
- bisect.insort(self.ckey_indexes[ckey], moment_index)
323
-
324
- def remove_op_from_moment(self, moment_index: int, op: cirq.Operation) -> None:
325
- self.ops_by_index[moment_index].pop(op)
326
- for q in op.qubits:
327
- if self.qubit_indexes[q][-1] == moment_index:
328
- self.qubit_indexes[q].pop()
329
- else:
330
- self.qubit_indexes[q].remove(moment_index)
331
- for mkey in protocols.measurement_key_objs(op):
332
- self.mkey_indexes[mkey].remove(moment_index)
333
- for ckey in protocols.control_keys(op):
334
- self.ckey_indexes[ckey].remove(moment_index)
335
-
336
- def get_mergeable_ops(
337
- self, op: cirq.Operation, op_qs: set[cirq.Qid]
338
- ) -> tuple[int, list[cirq.Operation]]:
339
- # Find the index of previous moment which can be merged with `op`.
340
- idx = max([self.qubit_indexes[q][-1] for q in op_qs], default=-1)
341
- idx = max([idx] + [self.mkey_indexes[ckey][-1] for ckey in protocols.control_keys(op)])
353
+ self.components_by_index.append({})
354
+
355
+ def add_component(self, c: Component) -> None:
356
+ """Adds a new components to merged circuit."""
357
+ self.components_by_index[c.moment_id][c] = None
358
+ for q in c.qubits:
359
+ _insort_last(self.qubit_indexes[q], c.moment_id)
360
+ for mkey in c.mkeys:
361
+ _insort_last(self.mkey_indexes[mkey], c.moment_id)
362
+ for ckey in c.ckeys:
363
+ _insort_last(self.ckey_indexes[ckey], c.moment_id)
364
+
365
+ def remove_component(self, c: Component, c_data: Component) -> None:
366
+ """Removes a component from the merged circuit.
367
+
368
+ Args:
369
+ c: Reference to the component to be removed.
370
+ c_data: Copy of the data in c before any component merges involving c
371
+ (this is necessary as component merges alter the component data).
372
+ """
373
+ self.components_by_index[c_data.moment_id].pop(c)
374
+ for q in c_data.qubits:
375
+ _remove_last(self.qubit_indexes[q], c_data.moment_id)
376
+ for mkey in c_data.mkeys:
377
+ _remove_last(self.mkey_indexes[mkey], c_data.moment_id)
378
+ for ckey in c_data.ckeys:
379
+ _remove_last(self.ckey_indexes[ckey], c_data.moment_id)
380
+
381
+ def get_mergeable_components(self, c: Component, c_qs: set[cirq.Qid]) -> list[Component]:
382
+ """Finds all components that can be merged with c.
383
+
384
+ Args:
385
+ c: Component to be merged with existing components.
386
+ c_qs: Subset of c.qubits used to decide which components are mergeable.
387
+
388
+ Returns:
389
+ List of mergeable components.
390
+ """
391
+ # Find the index of previous moment which can be merged with `c`.
342
392
  idx = max(
343
- [idx] + [self.ckey_indexes[mkey][-1] for mkey in protocols.measurement_key_objs(op)]
393
+ itertools.chain(
394
+ (self.qubit_indexes[q][-1] for q in c_qs),
395
+ (self.mkey_indexes[ckey][-1] for ckey in c.ckeys),
396
+ (self.ckey_indexes[mkey][-1] for mkey in c.mkeys),
397
+ ),
398
+ default=-1,
344
399
  )
345
- # Return the set of overlapping ops in moment with index `idx`.
400
+ # Return the set of overlapping components in moment with index `idx`.
346
401
  if idx == -1:
347
- return idx, []
402
+ return []
403
+
404
+ return [c for c in self.components_by_index[idx] if not c_qs.isdisjoint(c.qubits)]
405
+
406
+ def get_cirq_circuit(self, cset: ComponentSet, merged_circuit_op_tag: str) -> cirq.Circuit:
407
+ """Returns the merged circuit.
408
+
409
+ Args:
410
+ cset: Disjoint set data structure containing the components.
411
+ merged_circuit_op_tag: Tag to use for CircuitOperations.
412
+
413
+ Returns:
414
+ The circuit with merged components as a CircuitOperation.
415
+ """
416
+ component_ops: dict[Component, list[cirq.Operation]] = defaultdict(list)
417
+
418
+ # Traverse the components in creation order and collect operations
419
+ for c in cset.components():
420
+ root = cset.find(c)
421
+ component_ops[root].append(c.op)
422
+
423
+ moments = []
424
+ for m in self.components_by_index:
425
+ ops = []
426
+ for c in m.keys():
427
+ if isinstance(c, ComponentWithCircuitOp):
428
+ ops.append(c.circuit_op)
429
+ continue
430
+ if len(component_ops[c]) == 1:
431
+ ops.append(component_ops[c][0])
432
+ else:
433
+ ops.append(
434
+ circuits.CircuitOperation(
435
+ circuits.FrozenCircuit(component_ops[c])
436
+ ).with_tags(merged_circuit_op_tag)
437
+ )
438
+ moments.append(circuits.Moment(ops))
439
+ return circuits.Circuit(moments)
348
440
 
349
- return idx, [
350
- left_op for left_op in self.ops_by_index[idx] if not op_qs.isdisjoint(left_op.qubits)
351
- ]
352
441
 
353
- def get_cirq_circuit(self) -> cirq.Circuit:
354
- return circuits.Circuit(circuits.Moment(m.keys()) for m in self.ops_by_index)
442
+ def _merge_operations_impl(
443
+ circuit: CIRCUIT_TYPE,
444
+ cset: ComponentSet,
445
+ *,
446
+ merged_circuit_op_tag: str = "Merged connected component",
447
+ tags_to_ignore: Sequence[Hashable] = (),
448
+ deep: bool = False,
449
+ ) -> CIRCUIT_TYPE:
450
+ """Merges operations in a circuit.
451
+
452
+ Two operations op1 and op2 are merge-able if
453
+ - There is no other operations between op1 and op2 in the circuit
454
+ - is_subset(op1.qubits, op2.qubits) or is_subset(op2.qubits, op1.qubits)
455
+
456
+ The method iterates on the input circuit moment-by-moment from left to right and attempts
457
+ to repeatedly merge each operation in the latest moment with all the corresponding merge-able
458
+ operations to its left.
459
+
460
+ Operations are wrapped in a component and then cset.merge() is called to merge two
461
+ components.
462
+
463
+ If op1 and op2 are merged, both op1 and op2 are deleted from the circuit and
464
+ the merged component is inserted at the index corresponding to the larger
465
+ of op1/op2. If both op1 and op2 act on the same number of qubits, the merged component is
466
+ inserted in the smaller moment index to minimize circuit depth.
467
+
468
+ At the end every component with more than one operation is replaced by a CircuitOperation.
469
+
470
+ Args:
471
+ circuit: Input circuit to apply the transformations on. The input circuit is not mutated.
472
+ cset: Disjoint set data structure that is used to create and merge components.
473
+ merged_circuit_op_tag: Tag used for CircuitOperations created from merged components.
474
+ tags_to_ignore: Sequence of tags which should be ignored during the merge: operations with
475
+ these tags will not be merged.
476
+ deep: If true, the transformer primitive will be recursively applied to all circuits
477
+ wrapped inside circuit operations.
478
+
479
+
480
+ Returns:
481
+ Copy of input circuit with merged operations.
482
+ """
483
+ tags_to_ignore_set = set(tags_to_ignore)
484
+
485
+ merged_circuit = _MergedCircuit()
486
+ for moment_idx, current_moment in enumerate(circuit):
487
+ merged_circuit.append_empty_moment()
488
+ for op in sorted(current_moment.operations, key=lambda op: op.qubits):
489
+ if (
490
+ deep
491
+ and isinstance(op.untagged, circuits.CircuitOperation)
492
+ and tags_to_ignore_set.isdisjoint(op.tags)
493
+ ):
494
+ op_untagged = op.untagged
495
+ merged_op = op_untagged.replace(
496
+ circuit=_merge_operations_impl(
497
+ op_untagged.circuit,
498
+ cset,
499
+ merged_circuit_op_tag=merged_circuit_op_tag,
500
+ tags_to_ignore=tags_to_ignore,
501
+ deep=True,
502
+ )
503
+ ).with_tags(*op.tags)
504
+ c = cset.new_component(merged_op, moment_idx, is_mergeable=False)
505
+ merged_circuit.add_component(c)
506
+ continue
507
+
508
+ c = cset.new_component(
509
+ op, moment_idx, is_mergeable=tags_to_ignore_set.isdisjoint(op.tags)
510
+ )
511
+ if not c.is_mergeable:
512
+ merged_circuit.add_component(c)
513
+ continue
514
+
515
+ c_qs = set(c.qubits)
516
+ left_comp = merged_circuit.get_mergeable_components(c, c_qs)
517
+ if len(left_comp) == 1 and c_qs.issubset(left_comp[0].qubits):
518
+ # Make a shallow copy of the left component data before merge
519
+ left_c_data = copy.copy(left_comp[0])
520
+ # Case-1: Try to merge c with the larger component on the left.
521
+ new_comp = cset.merge(left_comp[0], c, merge_left=True)
522
+ if new_comp is not None:
523
+ merged_circuit.remove_component(left_comp[0], left_c_data)
524
+ merged_circuit.add_component(new_comp)
525
+ else:
526
+ merged_circuit.add_component(c)
527
+ continue
528
+
529
+ while left_comp and c_qs:
530
+ # Case-2: left_c will merge right into `c` whenever possible.
531
+ for left_c in left_comp:
532
+ is_merged = False
533
+ if c_qs.issuperset(left_c.qubits):
534
+ # Make a shallow copy of the left component data before merge
535
+ left_c_data = copy.copy(left_c)
536
+ # Try to merge left_c into c
537
+ new_comp = cset.merge(left_c, c, merge_left=False)
538
+ if new_comp is not None:
539
+ merged_circuit.remove_component(left_c, left_c_data)
540
+ c, is_merged = new_comp, True
541
+ if not is_merged:
542
+ c_qs -= left_c.qubits
543
+ left_comp = merged_circuit.get_mergeable_components(c, c_qs)
544
+ merged_circuit.add_component(c)
545
+ ret_circuit = merged_circuit.get_cirq_circuit(cset, merged_circuit_op_tag)
546
+ return _to_target_circuit_type(ret_circuit, circuit)
355
547
 
356
548
 
357
549
  def merge_operations(
@@ -407,12 +599,8 @@ def merge_operations(
407
599
  ValueError if the merged operation acts on new qubits outside the set of qubits
408
600
  corresponding to the original operations to be merged.
409
601
  """
410
- _circuit_op_tag = "_internal_tag_to_mark_circuit_ops_in_circuit"
411
- tags_to_ignore_set = set(tags_to_ignore) | {_circuit_op_tag}
412
602
 
413
603
  def apply_merge_func(op1: ops.Operation, op2: ops.Operation) -> ops.Operation | None:
414
- if not all(tags_to_ignore_set.isdisjoint(op.tags) for op in [op1, op2]):
415
- return None
416
604
  new_op = merge_func(op1, op2)
417
605
  qubit_set = frozenset(op1.qubits + op2.qubits)
418
606
  if new_op is not None and not qubit_set.issuperset(new_op.qubits):
@@ -422,63 +610,15 @@ def merge_operations(
422
610
  )
423
611
  return new_op
424
612
 
425
- merged_circuit = _MergedCircuit()
426
- for moment_idx, current_moment in enumerate(cast(list['cirq.Moment'], circuit)):
427
- merged_circuit.append_empty_moment()
428
- for op in sorted(current_moment.operations, key=lambda op: op.qubits):
429
- if (
430
- deep
431
- and isinstance(op.untagged, circuits.CircuitOperation)
432
- and tags_to_ignore_set.isdisjoint(op.tags)
433
- ):
434
- op_untagged = op.untagged
435
- merged_circuit.add_op_to_moment(
436
- moment_idx,
437
- op_untagged.replace(
438
- circuit=merge_operations(
439
- op_untagged.circuit,
440
- merge_func,
441
- tags_to_ignore=tags_to_ignore,
442
- deep=True,
443
- )
444
- ).with_tags(*op.tags, _circuit_op_tag),
445
- )
446
- continue
447
-
448
- op_qs = set(op.qubits)
449
- left_idx, left_ops = merged_circuit.get_mergeable_ops(op, op_qs)
450
- if len(left_ops) == 1 and op_qs.issubset(left_ops[0].qubits):
451
- # Case-1: Try to merge op with the larger operation on the left.
452
- new_op = apply_merge_func(left_ops[0], op)
453
- if new_op is not None:
454
- merged_circuit.remove_op_from_moment(left_idx, left_ops[0])
455
- merged_circuit.add_op_to_moment(left_idx, new_op)
456
- else:
457
- merged_circuit.add_op_to_moment(moment_idx, op)
458
- continue
613
+ def is_mergeable(_: cirq.Operation):
614
+ return True
459
615
 
460
- while left_ops and op_qs:
461
- # Case-2: left_ops will merge right into `op` whenever possible.
462
- for left_op in left_ops:
463
- is_merged = False
464
- if op_qs.issuperset(left_op.qubits):
465
- # Try to merge left_op into op
466
- new_op = apply_merge_func(left_op, op)
467
- if new_op is not None:
468
- merged_circuit.remove_op_from_moment(left_idx, left_op)
469
- op, is_merged = new_op, True
470
- if not is_merged:
471
- op_qs -= frozenset(left_op.qubits)
472
- left_idx, left_ops = merged_circuit.get_mergeable_ops(op, op_qs)
473
- merged_circuit.add_op_to_moment(moment_idx, op)
474
- ret_circuit = merged_circuit.get_cirq_circuit()
475
- if deep:
476
- ret_circuit = map_operations(
477
- ret_circuit,
478
- lambda o, _: o.untagged.with_tags(*(set(o.tags) - {_circuit_op_tag})),
479
- deep=True,
480
- )
481
- return _to_target_circuit_type(ret_circuit, circuit)
616
+ return _merge_operations_impl(
617
+ circuit,
618
+ ComponentWithCircuitOpSet(is_mergeable, apply_merge_func),
619
+ tags_to_ignore=tags_to_ignore,
620
+ deep=deep,
621
+ )
482
622
 
483
623
 
484
624
  def merge_operations_to_circuit_op(
@@ -491,10 +631,9 @@ def merge_operations_to_circuit_op(
491
631
  ) -> CIRCUIT_TYPE:
492
632
  """Merges connected components of operations and wraps each component into a circuit operation.
493
633
 
494
- Uses `cirq.merge_operations` to identify connected components of operations. Moment structure
495
- is preserved for operations that do not participate in merging. For merged operations, the
496
- newly created circuit operations are constructed by inserting operations using EARLIEST
497
- strategy.
634
+ Moment structure is preserved for operations that do not participate in merging.
635
+ For merged operations, the newly created circuit operations are constructed by inserting
636
+ operations using EARLIEST strategy.
498
637
  If you need more control on moment structure of newly created circuit operations, consider
499
638
  using `cirq.merge_operations` directly with a custom `merge_func`.
500
639
 
@@ -514,24 +653,16 @@ def merge_operations_to_circuit_op(
514
653
  Copy of input circuit with valid connected components wrapped in tagged circuit operations.
515
654
  """
516
655
 
517
- def merge_func(op1: cirq.Operation, op2: cirq.Operation) -> cirq.Operation | None:
518
- def get_ops(op: cirq.Operation):
519
- op_untagged = op.untagged
520
- return (
521
- [*op_untagged.circuit.all_operations()]
522
- if isinstance(op_untagged, circuits.CircuitOperation)
523
- and merged_circuit_op_tag in op.tags
524
- else [op]
525
- )
656
+ def is_mergeable(_: cirq.Operation):
657
+ return True
526
658
 
527
- left_ops, right_ops = get_ops(op1), get_ops(op2)
528
- if not can_merge(left_ops, right_ops):
529
- return None
530
- return circuits.CircuitOperation(circuits.FrozenCircuit(left_ops, right_ops)).with_tags(
531
- merged_circuit_op_tag
532
- )
533
-
534
- return merge_operations(circuit, merge_func, tags_to_ignore=tags_to_ignore, deep=deep)
659
+ return _merge_operations_impl(
660
+ circuit,
661
+ ComponentWithOpsSet(is_mergeable, can_merge),
662
+ merged_circuit_op_tag=merged_circuit_op_tag,
663
+ tags_to_ignore=tags_to_ignore,
664
+ deep=deep,
665
+ )
535
666
 
536
667
 
537
668
  def merge_k_qubit_unitaries_to_circuit_op(
@@ -544,10 +675,9 @@ def merge_k_qubit_unitaries_to_circuit_op(
544
675
  ) -> CIRCUIT_TYPE:
545
676
  """Merges connected components of operations, acting on <= k qubits, into circuit operations.
546
677
 
547
- Uses `cirq.merge_operations_to_circuit_op` to identify and merge connected components of
548
- unitary operations acting on at-most k-qubits. Moment structure is preserved for operations
549
- that do not participate in merging. For merged operations, the newly created circuit operations
550
- are constructed by inserting operations using EARLIEST strategy.
678
+ Moment structure is preserved for operations that do not participate in merging.
679
+ For merged operations, the newly created circuit operations are constructed by inserting
680
+ operations using EARLIEST strategy.
551
681
 
552
682
  Args:
553
683
  circuit: Input circuit to apply the transformations on. The input circuit is not mutated.
@@ -563,18 +693,14 @@ def merge_k_qubit_unitaries_to_circuit_op(
563
693
  Copy of input circuit with valid connected components wrapped in tagged circuit operations.
564
694
  """
565
695
 
566
- def can_merge(ops1: Sequence[cirq.Operation], ops2: Sequence[cirq.Operation]) -> bool:
567
- return all(
568
- protocols.num_qubits(op) <= k and protocols.has_unitary(op)
569
- for op_list in [ops1, ops2]
570
- for op in op_list
571
- )
696
+ def is_mergeable(op: cirq.Operation):
697
+ return protocols.num_qubits(op) <= k and protocols.has_unitary(op)
572
698
 
573
- return merge_operations_to_circuit_op(
699
+ return _merge_operations_impl(
574
700
  circuit,
575
- can_merge,
576
- tags_to_ignore=tags_to_ignore,
701
+ ComponentSet(is_mergeable),
577
702
  merged_circuit_op_tag=merged_circuit_op_tag or f"Merged {k}q unitary connected component.",
703
+ tags_to_ignore=tags_to_ignore,
578
704
  deep=deep,
579
705
  )
580
706