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
@@ -15,6 +15,7 @@
15
15
  from __future__ import annotations
16
16
 
17
17
  import itertools
18
+ from collections.abc import Iterator
18
19
 
19
20
  import numpy as np
20
21
  import pytest
@@ -23,11 +24,11 @@ import sympy
23
24
  import cirq
24
25
 
25
26
 
26
- def random_unitary(seed):
27
+ def random_unitary(seed: cirq.RANDOM_STATE_OR_SEED_LIKE) -> np.ndarray:
27
28
  return cirq.testing.random_unitary(4, random_state=seed)
28
29
 
29
30
 
30
- def random_locals(x, y, z, seed=None):
31
+ def random_locals(x: float, y: float, z: float, seed=None) -> np.ndarray:
31
32
  rng = np.random.RandomState(seed)
32
33
  a0 = cirq.testing.random_unitary(2, random_state=rng)
33
34
  a1 = cirq.testing.random_unitary(2, random_state=rng)
@@ -42,15 +43,14 @@ def random_locals(x, y, z, seed=None):
42
43
  )
43
44
 
44
45
 
45
- def perturbations_unitary(u, amount=1e-10):
46
+ def perturbations_unitary(u: np.ndarray, amount=1e-10) -> Iterator[np.ndarray]:
46
47
  """Returns several unitaries in the neighborhood of u to test for numerical
47
48
  corner cases near critical values."""
48
49
  kak = cirq.kak_decomposition(u)
49
50
  yield u
50
51
  for i in range(3):
51
52
  for neg in (-1, 1):
52
- perturb_xyz = list(kak.interaction_coefficients)
53
- perturb_xyz[i] += neg * amount
53
+ perturb_xyz = tuple(c + neg * amount for c in kak.interaction_coefficients)
54
54
  yield cirq.unitary(
55
55
  cirq.KakDecomposition(
56
56
  global_phase=kak.global_phase,
@@ -61,11 +61,11 @@ def perturbations_unitary(u, amount=1e-10):
61
61
  )
62
62
 
63
63
 
64
- def perturbations_gate(gate, amount=1e-10):
64
+ def perturbations_gate(gate: cirq.SupportsUnitary, amount=1e-10) -> Iterator[np.ndarray]:
65
65
  return perturbations_unitary(cirq.unitary(gate), amount)
66
66
 
67
67
 
68
- def perturbations_weyl(x, y, z, amount=1e-10):
68
+ def perturbations_weyl(x: float, y: float, z: float, amount: float = 1e-10) -> Iterator[np.ndarray]:
69
69
  return perturbations_gate(cirq.KakDecomposition(interaction_coefficients=(x, y, z)), amount)
70
70
 
71
71
 
@@ -211,7 +211,7 @@ def assert_valid_decomp(
211
211
  atol=1e-6,
212
212
  rtol=0,
213
213
  qubit_order=cirq.LineQubit.range(2),
214
- ):
214
+ ) -> None:
215
215
  # Check expected gates
216
216
  for op in operations:
217
217
  if len(op.qubits) == 0 and isinstance(op.gate, cirq.GlobalPhaseGate):
@@ -242,7 +242,7 @@ def assert_valid_decomp(
242
242
  )
243
243
 
244
244
 
245
- def assert_specific_sqrt_iswap_count(operations, count):
245
+ def assert_specific_sqrt_iswap_count(operations, count) -> None:
246
246
  actual = sum(len(op.qubits) == 2 for op in operations)
247
247
  assert actual == count, f'Incorrect sqrt-iSWAP count. Expected {count} but got {actual}.'
248
248
 
@@ -255,7 +255,7 @@ def assert_specific_sqrt_iswap_count(operations, count):
255
255
  cirq.CZPowGate(exponent=sympy.Symbol('t')),
256
256
  ],
257
257
  )
258
- def test_two_qubit_gates_with_symbols(gate: cirq.Gate):
258
+ def test_two_qubit_gates_with_symbols(gate: cirq.Gate) -> None:
259
259
  op = gate(*cirq.LineQubit.range(2))
260
260
  c_new_sqrt_iswap = cirq.Circuit(
261
261
  cirq.parameterized_2q_op_to_sqrt_iswap_operations(op) # type: ignore
@@ -279,12 +279,14 @@ def test_two_qubit_gates_with_symbols(gate: cirq.Gate):
279
279
  )
280
280
 
281
281
 
282
- def test_fsim_gate_with_symbols():
282
+ def test_fsim_gate_with_symbols() -> None:
283
283
  theta, phi = sympy.symbols(['theta', 'phi'])
284
284
  op = cirq.FSimGate(theta=theta, phi=phi).on(*cirq.LineQubit.range(2))
285
- c_new_sqrt_iswap = cirq.Circuit(cirq.parameterized_2q_op_to_sqrt_iswap_operations(op))
285
+ c_new_sqrt_iswap = cirq.Circuit(
286
+ cirq.parameterized_2q_op_to_sqrt_iswap_operations(op) # type: ignore[arg-type]
287
+ )
286
288
  c_new_sqrt_iswap_inv = cirq.Circuit(
287
- cirq.parameterized_2q_op_to_sqrt_iswap_operations(op, use_sqrt_iswap_inv=True)
289
+ cirq.parameterized_2q_op_to_sqrt_iswap_operations(op, use_sqrt_iswap_inv=True) # type: ignore[arg-type]
288
290
  )
289
291
  for theta_val in np.linspace(0, 2 * np.pi, 4):
290
292
  for phi_val in np.linspace(0, 2 * np.pi, 6):
@@ -307,7 +309,7 @@ def test_fsim_gate_with_symbols():
307
309
 
308
310
 
309
311
  @pytest.mark.parametrize('cnt', [-1, 4, 10])
310
- def test_invalid_required_sqrt_iswap_count(cnt):
312
+ def test_invalid_required_sqrt_iswap_count(cnt) -> None:
311
313
  u = TWO_SQRT_ISWAP_UNITARIES[0]
312
314
  q0, q1 = cirq.LineQubit.range(2)
313
315
  with pytest.raises(ValueError, match='required_sqrt_iswap_count'):
@@ -315,7 +317,7 @@ def test_invalid_required_sqrt_iswap_count(cnt):
315
317
 
316
318
 
317
319
  @pytest.mark.parametrize('u', ZERO_UNITARIES)
318
- def test_decomp0(u):
320
+ def test_decomp0(u) -> None:
319
321
  # Decompose unitaries into zero sqrt-iSWAP gates
320
322
  q0, q1 = cirq.LineQubit.range(2)
321
323
  ops = cirq.two_qubit_matrix_to_sqrt_iswap_operations(q0, q1, u, required_sqrt_iswap_count=0)
@@ -326,7 +328,7 @@ def test_decomp0(u):
326
328
  @pytest.mark.parametrize(
327
329
  'u', ONE_SQRT_ISWAP_UNITARIES + TWO_SQRT_ISWAP_UNITARIES + THREE_SQRT_ISWAP_UNITARIES
328
330
  )
329
- def test_decomp0_invalid(u):
331
+ def test_decomp0_invalid(u) -> None:
330
332
  # Attempt to decompose other unitaries into zero SQRT_ISWAP gates
331
333
  q0, q1 = cirq.LineQubit.range(2)
332
334
  with pytest.raises(ValueError, match='cannot be decomposed into exactly 0 sqrt-iSWAP gates'):
@@ -334,7 +336,7 @@ def test_decomp0_invalid(u):
334
336
 
335
337
 
336
338
  @pytest.mark.parametrize('u', ONE_SQRT_ISWAP_UNITARIES)
337
- def test_decomp1(u):
339
+ def test_decomp1(u) -> None:
338
340
  q0, q1 = cirq.LineQubit.range(2)
339
341
  ops = cirq.two_qubit_matrix_to_sqrt_iswap_operations(q0, q1, u, required_sqrt_iswap_count=1)
340
342
  assert_valid_decomp(u, ops)
@@ -344,14 +346,14 @@ def test_decomp1(u):
344
346
  @pytest.mark.parametrize(
345
347
  'u', ZERO_UNITARIES + TWO_SQRT_ISWAP_UNITARIES + THREE_SQRT_ISWAP_UNITARIES
346
348
  )
347
- def test_decomp1_invalid(u):
349
+ def test_decomp1_invalid(u) -> None:
348
350
  q0, q1 = cirq.LineQubit.range(2)
349
351
  with pytest.raises(ValueError, match='cannot be decomposed into exactly 1 sqrt-iSWAP gates'):
350
352
  cirq.two_qubit_matrix_to_sqrt_iswap_operations(q0, q1, u, required_sqrt_iswap_count=1)
351
353
 
352
354
 
353
355
  @pytest.mark.parametrize('u', ZERO_UNITARIES + ONE_SQRT_ISWAP_UNITARIES + TWO_SQRT_ISWAP_UNITARIES)
354
- def test_decomp2(u):
356
+ def test_decomp2(u) -> None:
355
357
  q0, q1 = cirq.LineQubit.range(2)
356
358
  ops = cirq.two_qubit_matrix_to_sqrt_iswap_operations(q0, q1, u, required_sqrt_iswap_count=2)
357
359
  assert_valid_decomp(u, ops)
@@ -359,7 +361,7 @@ def test_decomp2(u):
359
361
 
360
362
 
361
363
  @pytest.mark.parametrize('u', THREE_SQRT_ISWAP_UNITARIES)
362
- def test_decomp2_invalid(u):
364
+ def test_decomp2_invalid(u) -> None:
363
365
  q0, q1 = cirq.LineQubit.range(2)
364
366
  with pytest.raises(ValueError, match='cannot be decomposed into exactly 2 sqrt-iSWAP gates'):
365
367
  cirq.two_qubit_matrix_to_sqrt_iswap_operations(q0, q1, u, required_sqrt_iswap_count=2)
@@ -372,14 +374,14 @@ def test_decomp2_invalid(u):
372
374
  + TWO_SQRT_ISWAP_UNITARIES
373
375
  + THREE_SQRT_ISWAP_UNITARIES,
374
376
  )
375
- def test_decomp3(u):
377
+ def test_decomp3(u) -> None:
376
378
  q0, q1 = cirq.LineQubit.range(2)
377
379
  ops = cirq.two_qubit_matrix_to_sqrt_iswap_operations(q0, q1, u, required_sqrt_iswap_count=3)
378
380
  assert_valid_decomp(u, ops)
379
381
  assert_specific_sqrt_iswap_count(ops, 3)
380
382
 
381
383
 
382
- def test_decomp3_invalid():
384
+ def test_decomp3_invalid() -> None:
383
385
  # All two-qubit gates can be synthesized with three SQRT_ISWAP gates
384
386
  u = cirq.unitary(cirq.X**0.2) # Pass an invalid size unitary
385
387
  q0, q1 = cirq.LineQubit.range(2)
@@ -388,7 +390,7 @@ def test_decomp3_invalid():
388
390
 
389
391
 
390
392
  @pytest.mark.parametrize('u', TWO_SQRT_ISWAP_UNITARIES[:1])
391
- def test_qubit_order(u):
393
+ def test_qubit_order(u) -> None:
392
394
  q0, q1 = cirq.LineQubit.range(2)
393
395
  ops = cirq.two_qubit_matrix_to_sqrt_iswap_operations(q1, q0, u, required_sqrt_iswap_count=2)
394
396
  assert_valid_decomp(u, ops, qubit_order=(q1, q0))
@@ -396,7 +398,7 @@ def test_qubit_order(u):
396
398
 
397
399
 
398
400
  @pytest.mark.parametrize('u', ZERO_UNITARIES)
399
- def test_decomp_optimal0(u):
401
+ def test_decomp_optimal0(u) -> None:
400
402
  q0, q1 = cirq.LineQubit.range(2)
401
403
  ops = cirq.two_qubit_matrix_to_sqrt_iswap_operations(q0, q1, u)
402
404
  assert_valid_decomp(u, ops)
@@ -404,7 +406,7 @@ def test_decomp_optimal0(u):
404
406
 
405
407
 
406
408
  @pytest.mark.parametrize('u', ONE_SQRT_ISWAP_UNITARIES)
407
- def test_decomp_optimal1(u):
409
+ def test_decomp_optimal1(u) -> None:
408
410
  q0, q1 = cirq.LineQubit.range(2)
409
411
  ops = cirq.two_qubit_matrix_to_sqrt_iswap_operations(q0, q1, u)
410
412
  assert_valid_decomp(u, ops)
@@ -412,7 +414,7 @@ def test_decomp_optimal1(u):
412
414
 
413
415
 
414
416
  @pytest.mark.parametrize('u', TWO_SQRT_ISWAP_UNITARIES)
415
- def test_decomp_optimal2(u):
417
+ def test_decomp_optimal2(u) -> None:
416
418
  q0, q1 = cirq.LineQubit.range(2)
417
419
  ops = cirq.two_qubit_matrix_to_sqrt_iswap_operations(q0, q1, u)
418
420
  assert_valid_decomp(u, ops)
@@ -420,7 +422,7 @@ def test_decomp_optimal2(u):
420
422
 
421
423
 
422
424
  @pytest.mark.parametrize('u', THREE_SQRT_ISWAP_UNITARIES)
423
- def test_decomp_optimal3(u):
425
+ def test_decomp_optimal3(u) -> None:
424
426
  q0, q1 = cirq.LineQubit.range(2)
425
427
  ops = cirq.two_qubit_matrix_to_sqrt_iswap_operations(q0, q1, u)
426
428
  assert_valid_decomp(u, ops)
@@ -428,7 +430,7 @@ def test_decomp_optimal3(u):
428
430
 
429
431
 
430
432
  @pytest.mark.parametrize('u', ALL_REGION_UNITARIES)
431
- def test_all_weyl_regions(u):
433
+ def test_all_weyl_regions(u) -> None:
432
434
  q0, q1 = cirq.LineQubit.range(2)
433
435
  ops = cirq.two_qubit_matrix_to_sqrt_iswap_operations(q0, q1, u, clean_operations=True)
434
436
  assert_valid_decomp(u, ops, single_qubit_gate_types=(cirq.PhasedXZGate,))
@@ -445,13 +447,13 @@ def test_all_weyl_regions(u):
445
447
  THREE_SQRT_ISWAP_UNITARIES[0],
446
448
  ],
447
449
  )
448
- def test_decomp_sqrt_iswap_inv(u):
450
+ def test_decomp_sqrt_iswap_inv(u) -> None:
449
451
  q0, q1 = cirq.LineQubit.range(2)
450
452
  ops = cirq.two_qubit_matrix_to_sqrt_iswap_operations(q0, q1, u, use_sqrt_iswap_inv=True)
451
453
  assert_valid_decomp(u, ops, two_qubit_gate=cirq.SQRT_ISWAP_INV)
452
454
 
453
455
 
454
- def test_valid_check_raises():
456
+ def test_valid_check_raises() -> None:
455
457
  q0 = cirq.LineQubit(0)
456
458
  with pytest.raises(AssertionError, match='Unitaries are completely different'):
457
459
  assert_valid_decomp(np.eye(4), [cirq.X(q0)], single_qubit_gate_types=(cirq.XPowGate,))
@@ -19,13 +19,13 @@ import cirq
19
19
  NO_COMPILE_TAG = "no_compile_tag"
20
20
 
21
21
 
22
- def test_leaves_big():
22
+ def test_leaves_big() -> None:
23
23
  a = cirq.NamedQubit('a')
24
24
  circuit = cirq.Circuit(cirq.Moment(cirq.Z(a) ** 0.1))
25
25
  cirq.testing.assert_same_circuits(cirq.drop_negligible_operations(circuit, atol=0.001), circuit)
26
26
 
27
27
 
28
- def test_clears_small():
28
+ def test_clears_small() -> None:
29
29
  a = cirq.NamedQubit('a')
30
30
  circuit = cirq.Circuit(cirq.Moment(cirq.Z(a) ** 0.000001))
31
31
  cirq.testing.assert_same_circuits(
@@ -33,7 +33,7 @@ def test_clears_small():
33
33
  )
34
34
 
35
35
 
36
- def test_does_not_clear_small_no_compile():
36
+ def test_does_not_clear_small_no_compile() -> None:
37
37
  a = cirq.NamedQubit('a')
38
38
  circuit = cirq.Circuit(cirq.Moment((cirq.Z(a) ** 0.000001).with_tags(NO_COMPILE_TAG)))
39
39
  cirq.testing.assert_same_circuits(
@@ -44,7 +44,7 @@ def test_does_not_clear_small_no_compile():
44
44
  )
45
45
 
46
46
 
47
- def test_clears_known_empties_even_at_zero_tolerance():
47
+ def test_clears_known_empties_even_at_zero_tolerance() -> None:
48
48
  a, b = cirq.LineQubit.range(2)
49
49
  circuit = cirq.Circuit(
50
50
  cirq.Z(a) ** 0, cirq.Y(a) ** 0.0000001, cirq.X(a) ** -0.0000001, cirq.CZ(a, b) ** 0
@@ -63,7 +63,7 @@ def test_clears_known_empties_even_at_zero_tolerance():
63
63
  )
64
64
 
65
65
 
66
- def test_recursively_runs_inside_circuit_ops_deep():
66
+ def test_recursively_runs_inside_circuit_ops_deep() -> None:
67
67
  a = cirq.NamedQubit('a')
68
68
  small_op = cirq.Z(a) ** 0.000001
69
69
  nested_circuit = cirq.FrozenCircuit(
@@ -92,13 +92,13 @@ def test_recursively_runs_inside_circuit_ops_deep():
92
92
  ),
93
93
  cirq.Moment(),
94
94
  )
95
- context = cirq.TransformerContext(tags_to_ignore=[NO_COMPILE_TAG], deep=True)
95
+ context = cirq.TransformerContext(tags_to_ignore=(NO_COMPILE_TAG,), deep=True)
96
96
  cirq.testing.assert_same_circuits(
97
97
  cirq.drop_negligible_operations(c_orig, context=context, atol=0.001), c_expected
98
98
  )
99
99
 
100
100
 
101
- def test_ignores_large_ops():
101
+ def test_ignores_large_ops() -> None:
102
102
  qnum = 20
103
103
  qubits = cirq.LineQubit.range(qnum)
104
104
  subcircuit = cirq.FrozenCircuit(cirq.X.on_each(*qubits))
@@ -16,11 +16,13 @@
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
+ from enum import Enum
19
20
  from functools import reduce
20
21
  from itertools import cycle
21
22
  from typing import TYPE_CHECKING
22
23
 
23
24
  import numpy as np
25
+ from attrs import frozen
24
26
 
25
27
  from cirq import ops, protocols
26
28
  from cirq.circuits import Circuit, FrozenCircuit, Moment
@@ -133,10 +135,6 @@ def _calc_busy_moment_range_of_each_qubit(circuit: FrozenCircuit) -> dict[ops.Qi
133
135
  return busy_moment_range_by_qubit
134
136
 
135
137
 
136
- def _is_insertable_moment(moment: Moment, single_qubit_gate_moments_only: bool) -> bool:
137
- return not single_qubit_gate_moments_only or _is_single_qubit_gate_moment(moment)
138
-
139
-
140
138
  def _merge_single_qubit_ops_to_phxz(
141
139
  q: ops.Qid, operations: tuple[ops.Operation, ...]
142
140
  ) -> ops.Operation:
@@ -149,34 +147,162 @@ def _merge_single_qubit_ops_to_phxz(
149
147
  return gate.on(q)
150
148
 
151
149
 
152
- def _calc_pulled_through(moment: Moment, input_pauli_ops: ops.PauliString) -> ops.PauliString:
153
- """Calculates the pulled_through such that circuit(input_pauli_ops, moment.clifford_ops) is
154
- equivalent to circuit(moment.clifford_ops, pulled_through).
150
+ def _backward_set_stopping_slots(
151
+ q: ops.Qid,
152
+ from_mid: int,
153
+ mergable: dict[ops.Qid, dict[int, bool]],
154
+ need_to_stop: dict[ops.Qid, dict[int, bool]],
155
+ gate_types: dict[ops.Qid, dict[int, _CellType]],
156
+ circuit: FrozenCircuit,
157
+ ):
158
+ """Sets stopping slots for dynamical decoupling insertion.
159
+
160
+ This function traverses backward from a given moment `from_mid` for a specific qubit `q`.
161
+ It identifies moments where a dynamical decoupling sequence needs to be "stopped".
162
+
163
+ Args:
164
+ q: The qubit for which to set stopping slots.
165
+ from_mid: The moment ID to start the backward traversal from.
166
+ mergable: A dictionary indicating if a single-qubit Clifford gate at (qubit, moment_id)
167
+ can be merged with a Pauli gate.
168
+ need_to_stop: A dictionary to mark moments where a DD sequence must be stopped.
169
+ gate_types: A dictionary indicating the type of gate at each (qubit, moment_id).
170
+ circuit: The original frozen circuit.
155
171
  """
156
- clifford_ops_in_moment: list[ops.Operation] = [
157
- op for op in moment.operations if _is_clifford_op(op)
158
- ]
159
- return input_pauli_ops.after(clifford_ops_in_moment)
172
+ affected_qubits: set[ops.Qid] = {q}
173
+ for back_mid in range(from_mid, -1, -1):
174
+ for back_q in set(affected_qubits):
175
+ if gate_types[back_q][back_mid] == _CellType.WALL:
176
+ affected_qubits.remove(back_q)
177
+ continue
178
+ if mergable[back_q][back_mid]:
179
+ need_to_stop[back_q][back_mid] = True
180
+ affected_qubits.remove(back_q)
181
+ continue
182
+ op_at_q = circuit[back_mid].operation_at(back_q) or ops.I(q)
183
+ affected_qubits.update(op_at_q.qubits)
184
+ if not affected_qubits:
185
+ break
186
+
187
+
188
+ class _CellType(Enum):
189
+ UNKNOWN = '?'
190
+ # Non-insertable gates that cannot be pulled through
191
+ WALL = 'w'
192
+ # Clifford gates where Pauli Gates can be pulled through
193
+ DOOR = 'd'
194
+ # An empty gate can be used to insert Pauli gates from the dd sequence
195
+ INSERTABLE = 'i'
196
+
197
+
198
+ @frozen
199
+ class _Grid:
200
+ """A grid representation of the circuit where each gate position is labeled for
201
+ dynamical decoupling.
202
+
203
+ With this representation, a DD sequence can be automatically navigated in a
204
+ forward-only process. This avoids issues where a partially inserted DD
205
+ sequence encounters a "wall" and a new moment must be inserted because the
206
+ remaining DD sequence cannot be absorbed by nearby gates.
207
+
208
+ This labeled representation pre-calculates where DD pulses can be inserted
209
+ and where leftover DD sequences must be merged, avoiding the need for
210
+ backtracking.
211
+
212
+ An example labeled circuit is shown below:
213
+ | 0 | 1 | 2 | 3 | 4 |
214
+ -----+-----+-----+-----+-----+-----+
215
+ q(0) | d | i | i,s | d | w |
216
+ q(1) | d | i | d,s | w | w |
217
+ q(2) | d | d | d,s | w | w |
218
+ where `w`=WALL, `d`=DOOR, `i`=INSERTABLE. `s` represents a stop gate,
219
+ meaning that any unfinished DD sequences must be merged at this gate.
220
+ """
221
+
222
+ gate_types: dict[ops.Qid, dict[int, _CellType]]
223
+ need_to_stop: dict[ops.Qid, dict[int, bool]]
224
+ circuit: FrozenCircuit
225
+
226
+ @classmethod
227
+ def from_circuit(
228
+ cls, circuit: cirq.FrozenCircuit, single_qubit_gate_moments_only: bool
229
+ ) -> _Grid:
230
+ gate_types: dict[ops.Qid, dict[int, _CellType]] = {
231
+ q: {mid: _CellType.UNKNOWN for mid in range(len(circuit))} for q in circuit.all_qubits()
232
+ }
233
+ mergable: dict[ops.Qid, dict[int, bool]] = {
234
+ q: {mid: False for mid in range(len(circuit))} for q in circuit.all_qubits()
235
+ }
236
+ busy_moment_range_by_qubit = _calc_busy_moment_range_of_each_qubit(circuit)
237
+
238
+ # Set gate types for each (q, mid)
239
+ for mid, moment in enumerate(circuit):
240
+ is_insertable_moment = (
241
+ not single_qubit_gate_moments_only or _is_single_qubit_gate_moment(moment)
242
+ )
243
+ for q in circuit.all_qubits():
244
+ if mid < busy_moment_range_by_qubit[q][0] or mid > busy_moment_range_by_qubit[q][1]:
245
+ gate_types[q][mid] = _CellType.WALL
246
+ continue
247
+ op_at_q = moment.operation_at(q)
248
+ if op_at_q is None:
249
+ if is_insertable_moment:
250
+ gate_types[q][mid] = _CellType.INSERTABLE
251
+ mergable[q][mid] = True
252
+ else:
253
+ gate_types[q][mid] = _CellType.DOOR
254
+ else:
255
+ if _is_clifford_op(op_at_q):
256
+ gate_types[q][mid] = _CellType.DOOR
257
+ mergable[q][mid] = _is_single_qubit_operation(op_at_q)
258
+ else:
259
+ gate_types[q][mid] = _CellType.WALL
260
+
261
+ need_to_stop: dict[ops.Qid, dict[int, bool]] = {
262
+ q: {mid: False for mid in range(len(circuit))} for q in circuit.all_qubits()
263
+ }
264
+ # Reversely find the last mergeable gate of each qubit, set them as need_to_stop.
265
+ for q in circuit.all_qubits():
266
+ _backward_set_stopping_slots(
267
+ q, len(circuit) - 1, mergable, need_to_stop, gate_types, circuit
268
+ )
269
+ # Reversely check for each wall gate, mark the closest mergeable gate as need_to_stop.
270
+ for mid in range(len(circuit)):
271
+ for q in circuit.all_qubits():
272
+ if gate_types[q][mid] == _CellType.WALL:
273
+ _backward_set_stopping_slots(
274
+ q, mid - 1, mergable, need_to_stop, gate_types, circuit
275
+ )
276
+ return cls(circuit=circuit, gate_types=gate_types, need_to_stop=need_to_stop)
160
277
 
278
+ def __str__(self) -> str:
279
+ if not self.gate_types:
280
+ return "Grid(empty)"
161
281
 
162
- def _get_stop_qubits(moment: Moment) -> set[ops.Qid]:
163
- stop_pulling_through_qubits: set[ops.Qid] = set()
164
- for op in moment:
165
- if (not _is_clifford_op(op) and not _is_single_qubit_operation(op)) or not has_unitary(
166
- op
167
- ): # multi-qubit clifford op or non-mergable op.
168
- stop_pulling_through_qubits.update(op.qubits)
169
- return stop_pulling_through_qubits
282
+ qubits = sorted(list(self.gate_types.keys()))
283
+ num_moments = len(self.gate_types[qubits[0]])
170
284
 
285
+ max_qubit_len = max(len(str(q)) for q in qubits) if qubits else 0
171
286
 
172
- def _need_merge_pulled_through(op_at_q: ops.Operation, is_at_last_busy_moment: bool) -> bool:
173
- """With a pulling through pauli gate before op_at_q, need to merge with the
174
- pauli in the conditions below."""
175
- # The op must be mergable and single-qubit
176
- if not (_is_single_qubit_operation(op_at_q) and has_unitary(op_at_q)):
177
- return False
178
- # Either non-Clifford or at the last busy moment
179
- return is_at_last_busy_moment or not _is_clifford_op(op_at_q)
287
+ header = f"{'':>{max_qubit_len}} |"
288
+ for i in range(num_moments):
289
+ header += f" {i:^3} |"
290
+
291
+ separator = f"{'-' * max_qubit_len}-+"
292
+ separator += '-----+' * num_moments
293
+
294
+ lines = ["Grid Repr:", header, separator]
295
+
296
+ for q in qubits:
297
+ row_str = f"{str(q):>{max_qubit_len}} |"
298
+ for mid in range(num_moments):
299
+ gate_type = self.gate_types[q][mid].value
300
+ stop = self.need_to_stop[q][mid]
301
+ cell = f"{gate_type},s" if stop else f" {gate_type} "
302
+ row_str += f" {cell} |"
303
+ lines.append(row_str)
304
+
305
+ return "\n".join(lines)
180
306
 
181
307
 
182
308
  @transformer_api.transformer
@@ -188,7 +314,7 @@ def add_dynamical_decoupling(
188
314
  single_qubit_gate_moments_only: bool = True,
189
315
  ) -> cirq.Circuit:
190
316
  """Adds dynamical decoupling gate operations to a given circuit.
191
- This transformer might add new moments and thus change the structure of the original circuit.
317
+ This transformer preserves the structure of the original circuit.
192
318
 
193
319
  Args:
194
320
  circuit: Input circuit to transform.
@@ -202,11 +328,18 @@ def add_dynamical_decoupling(
202
328
  Returns:
203
329
  A copy of the input circuit with dynamical decoupling operations.
204
330
  """
205
- base_dd_sequence, pauli_map = _parse_dd_sequence(schema)
331
+
332
+ if context is not None and context.deep:
333
+ raise ValueError("Deep transformation is not supported.")
334
+
206
335
  orig_circuit = circuit.freeze()
207
336
 
208
- busy_moment_range_by_qubit = _calc_busy_moment_range_of_each_qubit(orig_circuit)
337
+ grid = _Grid.from_circuit(orig_circuit, single_qubit_gate_moments_only)
338
+
339
+ if context is not None and context.logger is not None:
340
+ context.logger.log("Preprocessed input circuit grid repr:\n%s", str(grid))
209
341
 
342
+ base_dd_sequence, pauli_map = _parse_dd_sequence(schema)
210
343
  # Stores all the moments of the output circuit chronologically.
211
344
  transformed_moments: list[Moment] = []
212
345
  # A PauliString stores the result of 'pulling' Pauli gates past each operations
@@ -215,90 +348,30 @@ def add_dynamical_decoupling(
215
348
  # Iterator of gate to be used in dd sequence for each qubit.
216
349
  dd_iter_by_qubits = {q: cycle(base_dd_sequence) for q in circuit.all_qubits()}
217
350
 
218
- def _update_pulled_through(q: ops.Qid, insert_gate: ops.Gate) -> ops.Operation:
219
- nonlocal pulled_through, pauli_map
220
- pulled_through *= pauli_map[insert_gate].on(q)
221
- return insert_gate.on(q)
222
-
223
- # Insert and pull remaining Pauli ops through the whole circuit.
224
- # General ideas are
225
- # * Pull through Clifford gates.
226
- # * Stop at multi-qubit non-Clifford ops (and other non-mergable ops).
227
- # * Merge to single-qubit non-Clifford ops.
228
- # * Insert a new moment if necessary.
229
- # After pulling through pulled_through at `moment`, we expect a transformation of
230
- # (pulled_through, moment) -> (updated_moment, updated_pulled_through) or
231
- # (pulled_through, moment) -> (new_moment, updated_moment, updated_pulled_through)
232
- # Moments structure changes are split into 3 steps:
233
- # 1, (..., last_moment, pulled_through1, moment, ...)
234
- # -> (..., last_moment, new_moment or None, pulled_through2, moment, ...)
235
- # 2, (..., pulled_through2, moment, ...) -> (..., pulled_through3, updated_moment, ...)
236
- # 3, (..., pulled_through3, updated_moment, ...)
237
- # -> (..., updated_moment, pulled_through4, ...)
238
351
  for moment_id, moment in enumerate(orig_circuit.moments):
239
- # Step 1, insert new_moment if necessary.
240
- # In detail: stop pulling through for multi-qubit non-Clifford ops or gates without
241
- # unitary representation (e.g., measure gates). If there are remaining pulled through ops,
242
- # insert into a new moment before current moment.
243
- stop_pulling_through_qubits: set[ops.Qid] = _get_stop_qubits(moment)
244
- new_moment_ops: list[ops.Operation] = []
245
- for q in stop_pulling_through_qubits:
246
- # Insert the remaining pulled_through
247
- remaining_pulled_through_gate = pulled_through.get(q)
248
- if remaining_pulled_through_gate is not None:
249
- new_moment_ops.append(_update_pulled_through(q, remaining_pulled_through_gate))
250
- # Reset dd sequence
251
- dd_iter_by_qubits[q] = cycle(base_dd_sequence)
252
- # Need to insert a new moment before current moment
253
- if new_moment_ops:
254
- # Fill insertable idle moments in the new moment using dd sequence
255
- for q in orig_circuit.all_qubits() - stop_pulling_through_qubits:
256
- if busy_moment_range_by_qubit[q][0] < moment_id <= busy_moment_range_by_qubit[q][1]:
257
- new_moment_ops.append(_update_pulled_through(q, next(dd_iter_by_qubits[q])))
258
- transformed_moments.append(Moment(new_moment_ops))
259
-
260
- # Step 2, calc updated_moment with insertions / merges.
261
352
  updated_moment_ops: set[cirq.Operation] = set()
262
353
  for q in orig_circuit.all_qubits():
263
- op_at_q = moment.operation_at(q)
264
- remaining_pulled_through_gate = pulled_through.get(q)
265
- updated_op = op_at_q
266
- if op_at_q is None: # insert into idle op
267
- if not _is_insertable_moment(moment, single_qubit_gate_moments_only):
268
- continue
269
- if (
270
- busy_moment_range_by_qubit[q][0] < moment_id < busy_moment_range_by_qubit[q][1]
271
- ): # insert next pauli gate in the dd sequence
272
- updated_op = _update_pulled_through(q, next(dd_iter_by_qubits[q]))
273
- elif ( # insert the remaining pulled through if beyond the ending busy moment
274
- moment_id > busy_moment_range_by_qubit[q][1]
275
- and remaining_pulled_through_gate is not None
276
- ):
277
- updated_op = _update_pulled_through(q, remaining_pulled_through_gate)
278
- elif (
279
- remaining_pulled_through_gate is not None
280
- ): # merge pulled-through of q to op_at_q if needed
281
- if _need_merge_pulled_through(
282
- op_at_q, moment_id == busy_moment_range_by_qubit[q][1]
283
- ):
284
- remaining_op = _update_pulled_through(q, remaining_pulled_through_gate)
285
- updated_op = _merge_single_qubit_ops_to_phxz(q, (remaining_op, op_at_q))
286
- if updated_op is not None:
287
- updated_moment_ops.add(updated_op)
288
-
289
- if updated_moment_ops:
290
- updated_moment = Moment(updated_moment_ops)
291
- transformed_moments.append(updated_moment)
292
-
293
- # Step 3, update pulled through.
294
- # In detail: pulling current `pulled_through` through updated_moment.
295
- pulled_through = _calc_pulled_through(updated_moment, pulled_through)
296
-
297
- # Insert a new moment if there are remaining pulled-through operations.
298
- ending_moment_ops = []
299
- for affected_q, combined_op_in_pauli in pulled_through.items():
300
- ending_moment_ops.append(combined_op_in_pauli.on(affected_q))
301
- if ending_moment_ops:
302
- transformed_moments.append(Moment(ending_moment_ops))
354
+ new_op_at_q = moment.operation_at(q)
355
+ if grid.gate_types[q][moment_id] == _CellType.INSERTABLE:
356
+ new_gate = next(dd_iter_by_qubits[q])
357
+ new_op_at_q = new_gate.on(q)
358
+ pulled_through *= pauli_map[new_gate].on(q)
359
+ if grid.need_to_stop[q][moment_id]:
360
+ to_be_merged = pulled_through.get(q)
361
+ if to_be_merged is not None:
362
+ new_op_at_q = _merge_single_qubit_ops_to_phxz(
363
+ q, (to_be_merged.on(q), new_op_at_q or ops.I(q))
364
+ )
365
+ pulled_through *= to_be_merged.on(q)
366
+ if new_op_at_q is not None:
367
+ updated_moment_ops.add(new_op_at_q)
368
+
369
+ updated_moment = Moment(updated_moment_ops)
370
+ clifford_ops = [op for op in updated_moment if _is_clifford_op(op)]
371
+ pulled_through = pulled_through.after(clifford_ops)
372
+ transformed_moments.append(updated_moment)
373
+
374
+ if len(pulled_through) > 0:
375
+ raise RuntimeError("Expect empty remaining Paulis after the dd insertion.")
303
376
 
304
377
  return Circuit.from_moments(*transformed_moments)