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
@@ -0,0 +1,680 @@
1
+ # Copyright 2025 The Cirq Developers
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ r"""Converts Cirq circuits to Quantikz LaTeX (using modern quantikz syntax).
16
+
17
+ This module provides a class, `CircuitToQuantikz`, to translate `cirq.Circuit`
18
+ objects into LaTeX code using the `quantikz` package. It aims to offer
19
+ flexible customization for gate styles, wire labels, and circuit folding.
20
+
21
+ Example:
22
+ >>> import cirq
23
+ >>> from cirq.contrib.quantikz import CircuitToQuantikz
24
+ >>> q0, q1 = cirq.LineQubit.range(2)
25
+ >>> circuit = cirq.Circuit(
26
+ ... cirq.H(q0),
27
+ ... cirq.CNOT(q0, q1),
28
+ ... cirq.measure(q0, key='m0'),
29
+ ... cirq.Rx(rads=0.5).on(q1)
30
+ ... )
31
+ >>> converter = CircuitToQuantikz(circuit, fold_at=2)
32
+ >>> latex_code = converter.generate_latex_document()
33
+ >>> print(latex_code)
34
+ \documentclass[preview, border=2pt]{standalone}
35
+ % Core drawing packages
36
+ \usepackage{tikz}
37
+ \usetikzlibrary{quantikz} % Loads the quantikz library (latest installed version)
38
+ % Optional useful TikZ libraries
39
+ \usetikzlibrary{fit, arrows.meta, decorations.pathreplacing, calligraphy}
40
+ % Font encoding and common math packages
41
+ \usepackage[T1]{fontenc}
42
+ \usepackage{amsmath}
43
+ \usepackage{amsfonts}
44
+ \usepackage{amssymb}
45
+ % --- Custom Preamble Injection Point ---
46
+ % --- End Custom Preamble ---
47
+ \begin{document}
48
+ \begin{quantikz}
49
+ \lstick{$q(0)$} & \gate[style={fill=yellow!20}]{H} & \qw & \rstick{$q(0)$} \\
50
+ \lstick{$q(1)$} & \qw & \qw & \rstick{$q(1)$}
51
+ \end{quantikz}
52
+ <BLANKLINE>
53
+ \vspace{1em}
54
+ <BLANKLINE>
55
+ \begin{quantikz}
56
+ \lstick{$q(0)$} & \ctrl{1} & \meter[style={fill=gray!20}]{m0} & \qw & \rstick{$q(0)$} \\
57
+ \lstick{$q(1)$} & \targ{} & \gate[style={fill=green!20}]{R_{X}(0.159\pi)} & \qw & \rstick{$q(1)$}
58
+ \end{quantikz}
59
+ \end{document}
60
+ """ # noqa: E501
61
+
62
+ from __future__ import annotations
63
+
64
+ import math
65
+ import warnings
66
+ from typing import Any
67
+
68
+ import sympy
69
+
70
+ from cirq import circuits, ops, protocols, value
71
+
72
+ DEFAULT_PREAMBLE_TEMPLATE = r"""
73
+ \documentclass[preview, border=2pt]{standalone}
74
+ % Core drawing packages
75
+ \usepackage{tikz}
76
+ \usetikzlibrary{quantikz} % Loads the quantikz library (latest installed version)
77
+ % Optional useful TikZ libraries
78
+ \usetikzlibrary{fit, arrows.meta, decorations.pathreplacing, calligraphy}
79
+ % Font encoding and common math packages
80
+ \usepackage[T1]{fontenc}
81
+ \usepackage{amsmath}
82
+ \usepackage{amsfonts}
83
+ \usepackage{amssymb}
84
+ """.lstrip()
85
+
86
+ # =============================================================================
87
+ # Default Style Definitions
88
+ # =============================================================================
89
+ _Pauli_gate_style = r"style={fill=blue!20}"
90
+ _green_gate_style = r"style={fill=green!20}"
91
+ _yellow_gate_style = r"style={fill=yellow!20}" # For H
92
+ _orange_gate_style = r"style={fill=orange!20}" # For FSim, iSwap, etc.
93
+ _gray_gate_style = r"style={fill=gray!20}" # For Measure
94
+ _noisy_channel_style = r"style={fill=red!20}"
95
+
96
+ GATE_STYLES_COLORFUL = {
97
+ "H": _yellow_gate_style,
98
+ "_PauliX": _Pauli_gate_style, # ops.X(q_param)
99
+ "_PauliY": _Pauli_gate_style, # ops.Y(q_param)
100
+ "_PauliZ": _Pauli_gate_style, # ops.Z(q_param)
101
+ "X": _Pauli_gate_style, # For XPowGate(exponent=1)
102
+ "Y": _Pauli_gate_style, # For YPowGate(exponent=1)
103
+ "Z": _Pauli_gate_style, # For ZPowGate(exponent=1)
104
+ "X_pow": _green_gate_style, # For XPowGate(exponent!=1)
105
+ "Y_pow": _green_gate_style, # For YPowGate(exponent!=1)
106
+ "Z_pow": _green_gate_style, # For ZPowGate(exponent!=1)
107
+ "H_pow": _green_gate_style, # For HPowGate(exponent!=1)
108
+ "Rx": _green_gate_style,
109
+ "Ry": _green_gate_style,
110
+ "Rz": _green_gate_style,
111
+ "PhasedXZ": _green_gate_style,
112
+ "FSimGate": _orange_gate_style,
113
+ "FSim": _orange_gate_style, # Alias for FSimGate
114
+ "iSwap": _orange_gate_style, # For ISwapPowGate(exponent=1)
115
+ "iSwap_pow": _orange_gate_style, # For ISwapPowGate(exponent!=1)
116
+ "CZ_pow": _orange_gate_style, # For CZPowGate(exponent!=1)
117
+ "CX_pow": _orange_gate_style, # For CNotPowGate(exponent!=1)
118
+ "CXideal": "", # No fill for \ctrl \targ, let quantikz draw default
119
+ "CZideal": "", # No fill for \ctrl \control
120
+ "Swapideal": "", # No fill for \swap \targX
121
+ "Measure": _gray_gate_style,
122
+ "DepolarizingChannel": _noisy_channel_style,
123
+ "BitFlipChannel": _noisy_channel_style,
124
+ "ThermalChannel": _noisy_channel_style,
125
+ }
126
+
127
+
128
+ # Initialize gate maps globally as recommended
129
+ _SIMPLE_GATE_MAP: dict[type[ops.Gate], str] = {ops.MeasurementGate: "Measure"}
130
+ _EXPONENT_GATE_MAP: dict[type[ops.Gate], str] = {
131
+ ops.XPowGate: "X",
132
+ ops.YPowGate: "Y",
133
+ ops.ZPowGate: "Z",
134
+ ops.HPowGate: "H",
135
+ ops.CNotPowGate: "CX",
136
+ ops.CZPowGate: "CZ",
137
+ ops.SwapPowGate: "Swap",
138
+ ops.ISwapPowGate: "iSwap",
139
+ }
140
+ _GATE_NAME_MAP = {
141
+ "Rx": r"R_{X}",
142
+ "Ry": r"R_{Y}",
143
+ "Rz": r"R_{Z}",
144
+ "FSim": r"\mathrm{fSim}",
145
+ "PhasedXZ": r"\Phi",
146
+ "CZ": r"\mathrm{CZ}",
147
+ "CX": r"\mathrm{CX}",
148
+ "iSwap": r"i\mathrm{SWAP}",
149
+ }
150
+ _PARAMETERIZED_GATE_BASE_NAMES: dict[type[ops.Gate], str] = {
151
+ ops.Rx: "Rx",
152
+ ops.Ry: "Ry",
153
+ ops.Rz: "Rz",
154
+ ops.PhasedXZGate: "PhasedXZ",
155
+ ops.FSimGate: "FSim",
156
+ }
157
+
158
+
159
+ # =============================================================================
160
+ # Cirq to Quantikz Conversion Class
161
+ # =============================================================================
162
+ class CircuitToQuantikz:
163
+ r"""Converts a Cirq Circuit object to a Quantikz LaTeX string.
164
+
165
+ This class facilitates the conversion of a `cirq.Circuit` into a LaTeX
166
+ representation using the `quantikz` package. It handles various gate types,
167
+ qubit mapping, and provides options for customizing the output, such as
168
+ gate styling, circuit folding, and parameter display.
169
+
170
+ Args:
171
+ circuit: The `cirq.Circuit` object to be converted.
172
+ gate_styles: An optional dictionary mapping gate names (strings) to
173
+ Quantikz style options (strings). These styles are applied to
174
+ the generated gates. If `None`, `GATE_STYLES_COLORFUL` is used.
175
+ quantikz_options: An optional string of global options to pass to the
176
+ `quantikz` environment (e.g., `"[row sep=0.5em]"`).
177
+ fold_at: An optional integer specifying the number of moments after
178
+ which the circuit should be folded into a new line in the LaTeX
179
+ output. If `None`, the circuit is not folded.
180
+ custom_preamble: An optional string containing custom LaTeX code to be
181
+ inserted into the document's preamble.
182
+ custom_postamble: An optional string containing custom LaTeX code to be
183
+ inserted just before `\end{document}`.
184
+ wire_labels: A string specifying how qubit wire labels should be
185
+ rendered.
186
+ - `"q"`: Labels as $q_0, q_1, \dots$
187
+ - `"index"`: Labels as $0, 1, \dots$
188
+ - `"qid"`: Labels as the string representation of the `cirq.Qid`
189
+ - Any other value defaults to `"qid"`.
190
+ show_parameters: A boolean indicating whether gate parameters (e.g.,
191
+ exponents for `XPowGate`, angles for `Rx`) should be displayed
192
+ in the gate labels.
193
+ gate_name_map: An optional dictionary mapping Cirq gate names (strings)
194
+ to custom LaTeX strings for rendering. This allows renaming gates
195
+ in the output.
196
+ float_precision_exps: An integer specifying the number of decimal
197
+ places for formatting floating-point exponents.
198
+ float_precision_angles: An integer specifying the number of decimal
199
+ places for formatting floating-point angles. (Note: Not fully
200
+ implemented in current version for all angle types).
201
+ qubit_order: Determines how qubits are ordered in the diagram.
202
+
203
+ Raises:
204
+ ValueError: If the input `circuit` is empty or contains no qubits.
205
+ """
206
+
207
+ def __init__(
208
+ self,
209
+ circuit: circuits.Circuit,
210
+ *,
211
+ gate_styles: dict[str, str] | None = None,
212
+ quantikz_options: str | None = None,
213
+ fold_at: int | None = None,
214
+ custom_preamble: str = "",
215
+ custom_postamble: str = "",
216
+ wire_labels: str = "qid",
217
+ show_parameters: bool = True,
218
+ gate_name_map: dict[str, str] | None = None,
219
+ float_precision_exps: int = 2,
220
+ float_precision_angles: int = 2,
221
+ qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
222
+ ):
223
+ if not circuit:
224
+ raise ValueError("Input circuit cannot be empty.")
225
+ self.circuit = circuit
226
+ self.gate_styles = gate_styles if gate_styles is not None else GATE_STYLES_COLORFUL.copy()
227
+ self.quantikz_options = quantikz_options or ""
228
+ self.fold_at = fold_at
229
+ self.custom_preamble = custom_preamble
230
+ self.custom_postamble = custom_postamble
231
+ self.wire_labels = wire_labels
232
+ self.show_parameters = show_parameters
233
+ self.current_gate_name_map = _GATE_NAME_MAP.copy()
234
+ if gate_name_map:
235
+ self.current_gate_name_map.update(gate_name_map)
236
+ self.sorted_qubits = ops.QubitOrder.as_qubit_order(qubit_order).order_for(
237
+ self.circuit.all_qubits()
238
+ )
239
+ if not self.sorted_qubits:
240
+ raise ValueError("Circuit contains no qubits.")
241
+ self.qubit_to_index = self._map_qubits_to_indices()
242
+ self.key_to_index = self._map_keys_to_indices()
243
+ self.num_qubits = len(self.sorted_qubits)
244
+ self.float_precision_exps = float_precision_exps
245
+ self.float_precision_angles = float_precision_angles
246
+
247
+ def _map_qubits_to_indices(self) -> dict[ops.Qid, int]:
248
+ """Creates a mapping from `cirq.Qid` objects to their corresponding
249
+ integer indices based on the sorted qubit order.
250
+
251
+ Returns:
252
+ A dictionary where keys are `cirq.Qid` objects and values are their
253
+ zero-based integer indices.
254
+ """
255
+ return {q: i for i, q in enumerate(self.sorted_qubits)}
256
+
257
+ def _map_keys_to_indices(self) -> dict[str, list[int]]:
258
+ """Maps measurement keys to qubit indices.
259
+
260
+ Used by classically controlled operations to map keys
261
+ to qubit wires.
262
+ """
263
+ key_map: dict[str, list[int]] = {}
264
+ for op in self.circuit.all_operations():
265
+ if isinstance(op.gate, ops.MeasurementGate):
266
+ key_map[op.gate.key] = [self.qubit_to_index[q] for q in op.qubits]
267
+ return key_map
268
+
269
+ def _escape_string(self, label) -> str:
270
+ """Escape labels for latex."""
271
+ label = label.replace("π", r"\pi")
272
+ if "_" in label and "\\" not in label:
273
+ label = label.replace("_", r"\_")
274
+ return label
275
+
276
+ def _get_wire_label(self, qubit: ops.Qid, index: int) -> str:
277
+ r"""Generates the LaTeX string for a qubit wire label.
278
+
279
+ Args:
280
+ qubit: The `cirq.Qid` object for which to generate the label.
281
+ index: The integer index of the qubit.
282
+
283
+ Returns:
284
+ A string formatted as a LaTeX math-mode label (e.g., "$q_0$", "$3$",
285
+ or "$q_{qubit\_name}$").
286
+ """
287
+ lbl = (
288
+ f"q_{{{index}}}"
289
+ if self.wire_labels == "q"
290
+ else (
291
+ str(index) if self.wire_labels == "index" else str(self._escape_string(str(qubit)))
292
+ )
293
+ )
294
+ return f"${lbl}$"
295
+
296
+ def _format_exponent_for_display(self, exponent: Any) -> str:
297
+ """Formats a gate exponent for display in LaTeX.
298
+
299
+ Handles floats, integers, and `sympy.Basic` expressions, converting them
300
+ to a string representation suitable for LaTeX, including proper
301
+ handling of numerical precision and symbolic constants like pi.
302
+
303
+ Args:
304
+ exponent: The exponent value, which can be a float, int, or
305
+ `sympy.Basic` object.
306
+
307
+ Returns:
308
+ A string representing the formatted exponent, ready for LaTeX
309
+ insertion.
310
+ """
311
+ exp_str: str
312
+ # Dynamically create the format string based on self.float_precision_exps
313
+ float_format_string = f".{self.float_precision_exps}f"
314
+
315
+ if isinstance(exponent, float):
316
+ # If the float is an integer value (e.g., 2.0), display as integer string ("2")
317
+ if exponent.is_integer():
318
+ exp_str = str(int(exponent))
319
+ else:
320
+ # Format to the specified precision for rounding
321
+ rounded_str = format(exponent, float_format_string)
322
+ # Convert back to float and then to string to remove unnecessary trailing zeros
323
+ # e.g., if precision is 2, 0.5 -> "0.50" -> 0.5 -> "0.5"
324
+ # e.g., if precision is 2, 0.318 -> "0.32" -> 0.32 -> "0.32"
325
+ exp_str = str(float(rounded_str))
326
+ # Check for sympy.Basic, assuming sympy is imported if this path is taken
327
+ elif isinstance(exponent, sympy.Basic):
328
+ s_exponent = str(exponent)
329
+ # Heuristic: check for letters to identify symbolic expressions
330
+ is_symbolic_or_special = any(
331
+ char.isalpha()
332
+ for char in s_exponent
333
+ if char.lower() not in ["e"] # Exclude 'e' for scientific notation
334
+ )
335
+ if not is_symbolic_or_special: # If it looks like a number
336
+ try:
337
+ py_float = float(sympy.N(exponent))
338
+ # If the sympy evaluated float is an integer value
339
+ if py_float.is_integer():
340
+ exp_str = str(int(py_float))
341
+ else:
342
+ # Format to specified precision for rounding
343
+ rounded_str = format(py_float, float_format_string)
344
+ # Convert back to float then to string to remove unnecessary trailing zeros
345
+ exp_str = str(float(rounded_str))
346
+ except (
347
+ TypeError,
348
+ ValueError,
349
+ AttributeError,
350
+ sympy.SympifyError,
351
+ ): # pragma: no cover
352
+ # Fallback to Sympy's string representation if conversion fails
353
+ exp_str = s_exponent
354
+ else: # Symbolic expression
355
+ exp_str = s_exponent
356
+ else: # For other types (int, strings not sympy objects)
357
+ exp_str = str(exponent)
358
+
359
+ return self._escape_string(exp_str)
360
+
361
+ def _get_gate_name(self, gate: ops.Gate) -> str:
362
+ """Determines the appropriate LaTeX string for a given Cirq gate.
363
+
364
+ This method attempts to derive a suitable LaTeX name for the gate,
365
+ considering its type, whether it's a power gate, and if parameters
366
+ should be displayed. It uses internal mappings and `cirq.circuit_diagram_info`.
367
+
368
+ Args:
369
+ gate: The `cirq.Gate` object to name.
370
+
371
+ Returns:
372
+ A string representing the LaTeX name of the gate (e.g., "H",
373
+ "Rx(0.5)", "CZ").
374
+ """
375
+ gate_type = type(gate)
376
+ if (simple_name := _SIMPLE_GATE_MAP.get(gate_type)) is not None:
377
+ return simple_name
378
+
379
+ base_key = _EXPONENT_GATE_MAP.get(gate_type)
380
+ if base_key is not None and hasattr(gate, "exponent") and gate.exponent == 1:
381
+ return self.current_gate_name_map.get(base_key, base_key)
382
+
383
+ if (param_base_key := _PARAMETERIZED_GATE_BASE_NAMES.get(gate_type)) is not None:
384
+ mapped_name = self.current_gate_name_map.get(param_base_key, param_base_key)
385
+ if not self.show_parameters:
386
+ return mapped_name
387
+ # Use protocols directly
388
+ info = protocols.circuit_diagram_info(gate, default=NotImplemented)
389
+ if info is not NotImplemented and info.wire_symbols:
390
+ s_diag = info.wire_symbols[0]
391
+ if (op_idx := s_diag.find("(")) != -1 and (cp_idx := s_diag.rfind(")")) > op_idx:
392
+ return (
393
+ f"{mapped_name}"
394
+ f"({self._format_exponent_for_display(s_diag[op_idx+1:cp_idx])})"
395
+ )
396
+ if hasattr(gate, "exponent") and not math.isclose(
397
+ gate.exponent, 1.0
398
+ ): # pragma: no cover
399
+ return f"{mapped_name}({self._format_exponent_for_display(gate.exponent)})"
400
+ return mapped_name # pragma: no cover
401
+
402
+ try:
403
+ # Use protocols directly
404
+ info = protocols.circuit_diagram_info(gate, default=NotImplemented)
405
+ if info is not NotImplemented and info.wire_symbols:
406
+ name_cand = info.wire_symbols[0]
407
+ if not self.show_parameters:
408
+ base_part = name_cand.split("^")[0].split("**")[0].split("(")[0].strip()
409
+ if isinstance(gate, ops.CZPowGate) and base_part == "@":
410
+ base_part = "CZ"
411
+ mapped_base = self.current_gate_name_map.get(base_part, base_part)
412
+ return self._format_exponent_for_display(mapped_base)
413
+
414
+ if (
415
+ hasattr(gate, "exponent")
416
+ and not (isinstance(gate.exponent, float) and math.isclose(gate.exponent, 1.0))
417
+ and isinstance(gate, tuple(_EXPONENT_GATE_MAP.keys()))
418
+ ):
419
+ has_exp_in_cand = ("^" in name_cand) or ("**" in name_cand)
420
+ if not has_exp_in_cand and base_key:
421
+ recon_base = self.current_gate_name_map.get(base_key, base_key)
422
+ needs_recon = (name_cand == base_key) or (
423
+ isinstance(gate, ops.CZPowGate) and name_cand == "@"
424
+ )
425
+ if needs_recon:
426
+ name_cand = (
427
+ f"{recon_base}^"
428
+ f"{{{self._format_exponent_for_display(gate.exponent)}}}"
429
+ )
430
+
431
+ fmt_name = self._escape_string(name_cand)
432
+ parts = fmt_name.split("**", 1)
433
+ if len(parts) == 2: # pragma: no cover
434
+ fmt_name = f"{parts[0]}^{{{self._format_exponent_for_display(parts[1])}}}"
435
+ return fmt_name
436
+ except (ValueError, AttributeError, IndexError): # pragma: no cover
437
+ # Fallback to default string representation if diagram info parsing fails.
438
+ pass
439
+
440
+ name_fb = str(gate)
441
+ if name_fb.endswith("**1.0"):
442
+ name_fb = name_fb[:-5]
443
+ if name_fb.endswith("**1"):
444
+ name_fb = name_fb[:-3]
445
+ if name_fb.endswith("()"):
446
+ name_fb = name_fb[:-2]
447
+ if name_fb.endswith("Gate"):
448
+ name_fb = name_fb[:-4]
449
+ if not self.show_parameters:
450
+ base_fb = name_fb.split("**")[0].split("(")[0].strip()
451
+ fb_key = _EXPONENT_GATE_MAP.get(gate_type, base_fb)
452
+ mapped_fb = self.current_gate_name_map.get(fb_key, fb_key)
453
+ return self._format_exponent_for_display(mapped_fb)
454
+ if "**" in name_fb:
455
+ parts = name_fb.split("**", 1)
456
+ if len(parts) == 2:
457
+ fb_key = _EXPONENT_GATE_MAP.get(gate_type, parts[0])
458
+ base_str_fb = self.current_gate_name_map.get(fb_key, parts[0])
459
+ name_fb = f"{base_str_fb}^{{{self._format_exponent_for_display(parts[1])}}}"
460
+ return self._escape_string(name_fb)
461
+
462
+ def _get_quantikz_options_string(self) -> str:
463
+ return f"[{self.quantikz_options}]" if self.quantikz_options else ""
464
+
465
+ def _render_operation(self, op: ops.Operation) -> dict[int, str]:
466
+ """Renders a single Cirq operation into its Quantikz LaTeX string representation.
467
+
468
+ Handles various gate types, including single-qubit gates, multi-qubit gates,
469
+ measurement gates, and special control/target gates (CNOT, CZ, SWAP).
470
+ Applies appropriate styles and labels based on the gate type and
471
+ `CircuitToQuantikz` instance settings.
472
+
473
+ Args:
474
+ op: The `cirq.Operation` object to render.
475
+
476
+ Returns:
477
+ A dictionary mapping qubit indices to their corresponding LaTeX strings
478
+ for the current moment.
479
+ """
480
+ output, q_indices = {}, sorted([self.qubit_to_index[q] for q in op.qubits])
481
+ gate = op.gate
482
+ if isinstance(op, ops.ClassicallyControlledOperation):
483
+ gate = op.without_classical_controls().gate
484
+ if gate is None: # pragma: no cover
485
+ raise ValueError(f'Only GateOperations are supported {op}')
486
+ gate_name_render = self._get_gate_name(gate)
487
+
488
+ gate_type = type(gate)
489
+ style_key = gate_type.__name__ # Default style key
490
+
491
+ # Determine style key based on gate type and properties
492
+ if isinstance(gate, ops.CNotPowGate) and gate.exponent == 1:
493
+ style_key = "CXideal"
494
+ elif isinstance(gate, ops.CZPowGate) and gate.exponent == 1:
495
+ style_key = "CZideal"
496
+ elif isinstance(gate, ops.SwapPowGate) and gate.exponent == 1:
497
+ style_key = "Swapideal"
498
+ elif isinstance(gate, ops.MeasurementGate):
499
+ style_key = "Measure"
500
+ elif (param_base_name := _PARAMETERIZED_GATE_BASE_NAMES.get(gate_type)) is not None:
501
+ style_key = param_base_name
502
+ elif (base_key_for_pow := _EXPONENT_GATE_MAP.get(gate_type)) is not None:
503
+ if getattr(gate, "exponent", 1) == 1:
504
+ style_key = base_key_for_pow
505
+ else:
506
+ style_key = f"{base_key_for_pow}_pow"
507
+
508
+ style_opts_str = self.gate_styles.get(style_key, "")
509
+ final_style_tikz = f"[{style_opts_str}]" if style_opts_str else ""
510
+
511
+ # Apply special Quantikz commands for specific gate types
512
+ if isinstance(gate, ops.MeasurementGate):
513
+ lbl = gate.key.replace("_", r"\_") if gate.key else ""
514
+ for idx, q1 in enumerate(q_indices):
515
+ if idx == 0:
516
+ output[q1] = f"\\meter{final_style_tikz}{{{lbl}}}"
517
+ else:
518
+ q0 = q_indices[idx - 1]
519
+ output[q1] = f"\\meter{final_style_tikz}{{}} \\vqw{{{q0-q1}}}"
520
+ elif isinstance(gate, ops.CNotPowGate) and gate.exponent == 1:
521
+ c, t = (
522
+ (self.qubit_to_index[op.qubits[0]], self.qubit_to_index[op.qubits[1]])
523
+ if len(op.qubits) == 2
524
+ else (q_indices[0], q_indices[0])
525
+ )
526
+ output[c] = f"\\ctrl{final_style_tikz}{{{t-c}}}"
527
+ output[t] = f"\\targ{final_style_tikz}{{}}"
528
+ elif isinstance(gate, ops.CZPowGate) and gate.exponent == 1:
529
+ i1, i2 = (
530
+ (q_indices[0], q_indices[1])
531
+ if len(q_indices) >= 2
532
+ else (q_indices[0], q_indices[0])
533
+ )
534
+ output[i1] = f"\\ctrl{final_style_tikz}{{{i2-i1}}}"
535
+ output[i2] = f"\\control{final_style_tikz}{{}}"
536
+ elif isinstance(gate, ops.SwapPowGate) and gate.exponent == 1:
537
+ i1, i2 = (
538
+ (q_indices[0], q_indices[1])
539
+ if len(q_indices) >= 2
540
+ else (q_indices[0], q_indices[0])
541
+ )
542
+ output[i1] = f"\\swap{final_style_tikz}{{{i2-i1}}}"
543
+ output[i2] = f"\\targX{final_style_tikz}{{}}"
544
+ # Handle generic \gate command for single and multi-qubit gates
545
+ elif len(q_indices) == 1:
546
+ output[q_indices[0]] = f"\\gate{final_style_tikz}{{{gate_name_render}}}"
547
+ else: # Multi-qubit gate
548
+ combined_opts = f"wires={q_indices[-1]-q_indices[0]+1}"
549
+ if style_opts_str:
550
+ combined_opts = f"{combined_opts}, {style_opts_str}"
551
+ output[q_indices[0]] = f"\\gate[{combined_opts}]{{{gate_name_render}}}"
552
+ for i in range(1, len(q_indices)):
553
+ output[q_indices[i]] = "\\qw"
554
+ if isinstance(op, ops.ClassicallyControlledOperation):
555
+ q0 = q_indices[0]
556
+ for key in op.classical_controls:
557
+ if isinstance(key, value.KeyCondition):
558
+ for index in self.key_to_index[key.key.name]:
559
+ output[q0] += f" \\vcw{{{index-q0}}}"
560
+ output[index] = "\\ctrl{}"
561
+
562
+ return output
563
+
564
+ def _initial_active_chunk(self) -> list[list[str]]:
565
+ """Add initial wire labels for the first chunk"""
566
+ return [
567
+ [f"\\lstick{{{self._get_wire_label(self.sorted_qubits[i],i)}}}"]
568
+ for i in range(self.num_qubits)
569
+ ]
570
+
571
+ def _generate_latex_body(self) -> str:
572
+ """Generates the main LaTeX body for the circuit diagram.
573
+
574
+ Iterates through the circuit's moments, renders each operation, and
575
+ arranges them into Quantikz environments. Supports circuit folding
576
+ into multiple rows if `fold_at` is specified.
577
+ Handles qubit wire labels and ensures correct LaTeX syntax.
578
+ """
579
+ chunks = []
580
+ active_chunk = self._initial_active_chunk()
581
+
582
+ for m_idx, moment in enumerate(self.circuit):
583
+ moment_out = ["\\qw"] * self.num_qubits
584
+
585
+ # Add LaTeX for each operation in the moment
586
+ spanned_qubits: set[int] = set()
587
+ for op in moment:
588
+ if not op.qubits:
589
+ warnings.warn(f"Op {op} no qubits.")
590
+ continue
591
+ min_qubit = min(self.qubit_to_index[q] for q in op.qubits)
592
+ max_qubit = max(self.qubit_to_index[q] for q in op.qubits)
593
+ for i in range(min_qubit, max_qubit + 1):
594
+ if i in spanned_qubits:
595
+ # This overlaps another operation:
596
+ # Create a new column.
597
+ for i in range(self.num_qubits):
598
+ active_chunk[i].append(moment_out[i])
599
+ moment_out = ["\\qw"] * self.num_qubits
600
+ spanned_qubits = set()
601
+ for i in range(min_qubit, max_qubit + 1):
602
+ spanned_qubits.add(i)
603
+ for q in op.qubits:
604
+ spanned_qubits.add(self.qubit_to_index[q])
605
+ op_rnd = self._render_operation(op)
606
+ for idx, tex in op_rnd.items():
607
+ moment_out[idx] = tex
608
+ for i in range(self.num_qubits):
609
+ active_chunk[i].append(moment_out[i])
610
+
611
+ is_last_m = m_idx == len(self.circuit) - 1
612
+ if self.fold_at and m_idx % self.fold_at == 0 and not is_last_m:
613
+ for i in range(self.num_qubits):
614
+ lbl = self._get_wire_label(self.sorted_qubits[i], i)
615
+ active_chunk[i].extend(["\\qw", f"\\rstick{{{lbl}}}"])
616
+ chunks.append(active_chunk)
617
+ active_chunk = self._initial_active_chunk()
618
+
619
+ for i in range(self.num_qubits):
620
+ active_chunk[i].append("\\qw")
621
+ if self.fold_at:
622
+ for i in range(self.num_qubits):
623
+ active_chunk[i].extend(
624
+ [f"\\rstick{{{self._get_wire_label(self.sorted_qubits[i],i)}}}"]
625
+ )
626
+ chunks.append(active_chunk)
627
+
628
+ opts_str = self._get_quantikz_options_string()
629
+ final_parts = []
630
+ for chunk_data in chunks:
631
+ lines = [f"\\begin{{quantikz}}{opts_str}"]
632
+ for i in range(self.num_qubits):
633
+ if i < len(chunk_data) and chunk_data[i]:
634
+ lines.append(" & ".join(chunk_data[i]) + " \\\\")
635
+
636
+ if len(lines) > 1:
637
+ for k_idx in range(len(lines) - 1, 0, -1):
638
+ stipped_line = lines[k_idx].strip()
639
+ if stipped_line:
640
+ if stipped_line != "\\\\":
641
+ if lines[k_idx].endswith(" \\\\"):
642
+ lines[k_idx] = lines[k_idx].rstrip()[:-3].rstrip()
643
+ break
644
+ elif k_idx == len(lines) - 1: # pragma: no cover
645
+ lines[k_idx] = ""
646
+ lines.append("\\end{quantikz}")
647
+ final_parts.append("\n".join(filter(None, lines)))
648
+
649
+ return "\n\n\\vspace{1em}\n\n".join(final_parts)
650
+
651
+ def generate_latex_document(self, preamble_template: str | None = None) -> str:
652
+ """Generates the complete LaTeX document string for the circuit.
653
+
654
+ Combines the preamble, custom preamble, generated circuit body,
655
+ and custom postamble into a single LaTeX document string.
656
+
657
+ Args:
658
+ preamble_template: An optional string to use as the base LaTeX
659
+ preamble. If `None`, `DEFAULT_PREAMBLE_TEMPLATE` is used.
660
+
661
+ Returns:
662
+ A string containing the full LaTeX document, ready to be compiled.
663
+ """
664
+ preamble = preamble_template or DEFAULT_PREAMBLE_TEMPLATE
665
+ doc_parts = [
666
+ preamble.rstrip(),
667
+ "% --- Custom Preamble Injection Point ---",
668
+ *([self.custom_preamble.rstrip()] if self.custom_preamble else []),
669
+ "% --- End Custom Preamble ---",
670
+ "\\begin{document}",
671
+ self._generate_latex_body(),
672
+ ]
673
+ if self.custom_postamble:
674
+ doc_parts.append(
675
+ f"% --- Custom Postamble Start ---\n"
676
+ f"{self.custom_postamble.rstrip()}\n"
677
+ f"% --- Custom Postamble End ---"
678
+ )
679
+ doc_parts.append("\\end{document}")
680
+ return "\n".join(doc_parts)