cirq-core 1.7.0.dev20250825174419__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 (426) 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 +19 -17
  10. cirq/circuits/circuit_operation.py +2 -1
  11. cirq/circuits/circuit_operation_test.py +19 -0
  12. cirq/circuits/circuit_test.py +31 -12
  13. cirq/circuits/frozen_circuit.py +3 -2
  14. cirq/circuits/moment.py +3 -15
  15. cirq/circuits/optimization_pass.py +2 -1
  16. cirq/circuits/qasm_output.py +39 -10
  17. cirq/circuits/qasm_output_test.py +51 -2
  18. cirq/circuits/text_diagram_drawer.py +2 -1
  19. cirq/contrib/acquaintance/bipartite.py +2 -1
  20. cirq/contrib/acquaintance/devices.py +1 -1
  21. cirq/contrib/acquaintance/executor.py +4 -5
  22. cirq/contrib/acquaintance/executor_test.py +2 -1
  23. cirq/contrib/acquaintance/gates.py +2 -1
  24. cirq/contrib/acquaintance/gates_test.py +1 -1
  25. cirq/contrib/acquaintance/inspection_utils.py +2 -1
  26. cirq/contrib/acquaintance/mutation_utils.py +2 -1
  27. cirq/contrib/acquaintance/optimizers.py +2 -1
  28. cirq/contrib/acquaintance/permutation.py +2 -1
  29. cirq/contrib/acquaintance/permutation_test.py +1 -1
  30. cirq/contrib/acquaintance/shift.py +2 -1
  31. cirq/contrib/acquaintance/shift_swap_network.py +2 -1
  32. cirq/contrib/acquaintance/strategies/complete.py +3 -2
  33. cirq/contrib/acquaintance/strategies/cubic.py +2 -1
  34. cirq/contrib/acquaintance/strategies/quartic_paired.py +2 -1
  35. cirq/contrib/acquaintance/strategies/quartic_paired_test.py +1 -1
  36. cirq/contrib/acquaintance/testing.py +2 -1
  37. cirq/contrib/acquaintance/topological_sort.py +2 -1
  38. cirq/contrib/bayesian_network/bayesian_network_gate.py +3 -2
  39. cirq/contrib/circuitdag/circuit_dag.py +4 -2
  40. cirq/contrib/custom_simulators/custom_state_simulator.py +2 -1
  41. cirq/contrib/custom_simulators/custom_state_simulator_test.py +1 -1
  42. cirq/contrib/graph_device/graph_device.py +2 -1
  43. cirq/contrib/graph_device/graph_device_test.py +2 -1
  44. cirq/contrib/graph_device/hypergraph.py +2 -1
  45. cirq/contrib/graph_device/uniform_graph_device.py +2 -1
  46. cirq/contrib/json.py +14 -2
  47. cirq/contrib/json_test_data/BayesianNetworkGate.json +10 -0
  48. cirq/contrib/json_test_data/BayesianNetworkGate.repr +3 -0
  49. cirq/contrib/json_test_data/QuantumVolumeResult.json +169 -0
  50. cirq/contrib/json_test_data/QuantumVolumeResult.repr +22 -0
  51. cirq/contrib/json_test_data/SwapPermutationGate.json +3 -0
  52. cirq/contrib/json_test_data/SwapPermutationGate.repr +1 -0
  53. cirq/contrib/json_test_data/spec.py +0 -2
  54. cirq/contrib/noise_models/noise_models.py +2 -1
  55. cirq/contrib/paulistring/clifford_optimize.py +20 -2
  56. cirq/contrib/paulistring/optimize.py +1 -1
  57. cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation.py +146 -35
  58. cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation_test.py +81 -178
  59. cirq/contrib/paulistring/recombine.py +5 -2
  60. cirq/contrib/paulistring/separate.py +1 -1
  61. cirq/contrib/qasm_import/_lexer.py +6 -1
  62. cirq/contrib/qasm_import/_lexer_test.py +1 -1
  63. cirq/contrib/qasm_import/_parser.py +24 -8
  64. cirq/contrib/qasm_import/_parser_test.py +44 -6
  65. cirq/contrib/qcircuit/qcircuit_pdf_test.py +6 -9
  66. cirq/contrib/quantikz/__init__.py +21 -0
  67. cirq/contrib/quantikz/circuit_to_latex_quantikz.py +680 -0
  68. cirq/contrib/quantikz/circuit_to_latex_quantikz_test.py +253 -0
  69. cirq/contrib/quantikz/circuit_to_latex_render.py +424 -0
  70. cirq/contrib/quantikz/circuit_to_latex_render_test.py +44 -0
  71. cirq/contrib/quantum_volume/quantum_volume.py +2 -1
  72. cirq/contrib/quimb/density_matrix.py +1 -1
  73. cirq/contrib/quimb/grid_circuits.py +2 -1
  74. cirq/contrib/quimb/grid_circuits_test.py +1 -1
  75. cirq/contrib/quimb/mps_simulator.py +4 -3
  76. cirq/contrib/quimb/state_vector.py +2 -1
  77. cirq/contrib/quirk/export_to_quirk.py +2 -1
  78. cirq/contrib/quirk/linearize_circuit.py +1 -1
  79. cirq/contrib/quirk/quirk_gate.py +2 -1
  80. cirq/contrib/routing/device.py +1 -1
  81. cirq/contrib/routing/greedy.py +2 -1
  82. cirq/contrib/routing/initialization.py +2 -1
  83. cirq/contrib/routing/router.py +2 -1
  84. cirq/contrib/routing/swap_network.py +2 -1
  85. cirq/contrib/routing/utils.py +2 -1
  86. cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py +7 -5
  87. cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py +6 -6
  88. cirq/devices/device.py +2 -1
  89. cirq/devices/grid_device_metadata.py +2 -1
  90. cirq/devices/grid_qubit.py +7 -6
  91. cirq/devices/insertion_noise_model.py +2 -1
  92. cirq/devices/line_qubit.py +2 -1
  93. cirq/devices/named_topologies.py +2 -1
  94. cirq/devices/noise_model.py +2 -1
  95. cirq/devices/noise_model_test.py +1 -1
  96. cirq/devices/noise_properties.py +2 -1
  97. cirq/devices/superconducting_qubits_noise_properties_test.py +2 -1
  98. cirq/devices/thermal_noise_model.py +2 -1
  99. cirq/experiments/__init__.py +2 -0
  100. cirq/experiments/benchmarking/parallel_xeb.py +2 -1
  101. cirq/experiments/benchmarking/parallel_xeb_test.py +1 -1
  102. cirq/experiments/fidelity_estimation.py +2 -1
  103. cirq/experiments/fidelity_estimation_test.py +1 -1
  104. cirq/experiments/ghz_2d.py +150 -0
  105. cirq/experiments/ghz_2d_test.py +155 -0
  106. cirq/experiments/n_qubit_tomography.py +2 -1
  107. cirq/experiments/n_qubit_tomography_test.py +1 -1
  108. cirq/experiments/purity_estimation.py +1 -1
  109. cirq/experiments/qubit_characterizations.py +33 -4
  110. cirq/experiments/qubit_characterizations_test.py +16 -0
  111. cirq/experiments/random_quantum_circuit_generation.py +2 -1
  112. cirq/experiments/random_quantum_circuit_generation_test.py +2 -1
  113. cirq/experiments/readout_confusion_matrix.py +2 -1
  114. cirq/experiments/readout_confusion_matrix_test.py +1 -1
  115. cirq/experiments/single_qubit_readout_calibration.py +2 -1
  116. cirq/experiments/single_qubit_readout_calibration_test.py +1 -1
  117. cirq/experiments/t1_decay_experiment.py +2 -1
  118. cirq/experiments/two_qubit_xeb.py +2 -1
  119. cirq/experiments/two_qubit_xeb_test.py +1 -1
  120. cirq/experiments/xeb_fitting.py +2 -1
  121. cirq/experiments/xeb_fitting_test.py +1 -1
  122. cirq/experiments/xeb_sampling.py +5 -3
  123. cirq/experiments/xeb_sampling_test.py +1 -1
  124. cirq/experiments/xeb_simulation.py +2 -1
  125. cirq/experiments/xeb_simulation_test.py +2 -1
  126. cirq/experiments/z_phase_calibration.py +2 -1
  127. cirq/experiments/z_phase_calibration_test.py +18 -3
  128. cirq/interop/quirk/cells/__init__.py +1 -2
  129. cirq/interop/quirk/cells/all_cells.py +2 -1
  130. cirq/interop/quirk/cells/arithmetic_cells.py +2 -1
  131. cirq/interop/quirk/cells/cell.py +2 -1
  132. cirq/interop/quirk/cells/composite_cell.py +2 -1
  133. cirq/interop/quirk/cells/composite_cell_test.py +1 -1
  134. cirq/interop/quirk/cells/control_cells.py +2 -1
  135. cirq/interop/quirk/cells/frequency_space_cells.py +1 -1
  136. cirq/interop/quirk/cells/ignored_cells.py +1 -1
  137. cirq/interop/quirk/cells/input_cells.py +2 -1
  138. cirq/interop/quirk/cells/input_rotation_cells.py +2 -1
  139. cirq/interop/quirk/cells/measurement_cells.py +2 -1
  140. cirq/interop/quirk/cells/parse.py +2 -11
  141. cirq/interop/quirk/cells/qubit_permutation_cells.py +2 -1
  142. cirq/interop/quirk/cells/scalar_cells.py +2 -1
  143. cirq/interop/quirk/cells/single_qubit_rotation_cells.py +2 -1
  144. cirq/interop/quirk/cells/swap_cell.py +2 -1
  145. cirq/interop/quirk/cells/unsupported_cells.py +1 -1
  146. cirq/interop/quirk/url_to_circuit.py +2 -1
  147. cirq/json_resolver_cache.py +0 -2
  148. cirq/linalg/decompositions.py +6 -2
  149. cirq/linalg/decompositions_test.py +1 -0
  150. cirq/linalg/diagonalize.py +1 -1
  151. cirq/linalg/predicates.py +2 -1
  152. cirq/linalg/tolerance.py +2 -1
  153. cirq/linalg/transformations.py +3 -2
  154. cirq/ops/arithmetic_operation.py +4 -3
  155. cirq/ops/arithmetic_operation_test.py +1 -1
  156. cirq/ops/boolean_hamiltonian.py +4 -3
  157. cirq/ops/classically_controlled_operation.py +11 -11
  158. cirq/ops/classically_controlled_operation_test.py +26 -2
  159. cirq/ops/clifford_gate.py +3 -2
  160. cirq/ops/clifford_gate_test.py +1 -2
  161. cirq/ops/common_channels.py +2 -1
  162. cirq/ops/common_gates.py +3 -2
  163. cirq/ops/control_values.py +2 -1
  164. cirq/ops/controlled_gate.py +3 -2
  165. cirq/ops/controlled_gate_test.py +2 -1
  166. cirq/ops/controlled_operation.py +3 -2
  167. cirq/ops/controlled_operation_test.py +2 -1
  168. cirq/ops/dense_pauli_string.py +44 -81
  169. cirq/ops/dense_pauli_string_test.py +21 -0
  170. cirq/ops/diagonal_gate.py +3 -2
  171. cirq/ops/eigen_gate.py +9 -7
  172. cirq/ops/fourier_transform.py +3 -2
  173. cirq/ops/fourier_transform_test.py +2 -4
  174. cirq/ops/fsim_gate.py +3 -2
  175. cirq/ops/gate_operation.py +23 -12
  176. cirq/ops/gateset.py +22 -2
  177. cirq/ops/global_phase_op.py +3 -2
  178. cirq/ops/greedy_qubit_manager.py +2 -1
  179. cirq/ops/identity.py +2 -1
  180. cirq/ops/kraus_channel.py +2 -1
  181. cirq/ops/linear_combinations.py +12 -17
  182. cirq/ops/linear_combinations_test.py +23 -1
  183. cirq/ops/matrix_gates.py +2 -1
  184. cirq/ops/measure_util.py +8 -6
  185. cirq/ops/measurement_gate.py +2 -1
  186. cirq/ops/mixed_unitary_channel.py +2 -1
  187. cirq/ops/named_qubit.py +2 -2
  188. cirq/ops/op_tree.py +2 -1
  189. cirq/ops/parallel_gate.py +3 -2
  190. cirq/ops/parity_gates.py +2 -1
  191. cirq/ops/parity_gates_test.py +35 -0
  192. cirq/ops/pauli_interaction_gate.py +2 -1
  193. cirq/ops/pauli_measurement_gate.py +2 -1
  194. cirq/ops/pauli_string.py +37 -57
  195. cirq/ops/pauli_string_phasor.py +6 -5
  196. cirq/ops/pauli_string_raw_types.py +2 -1
  197. cirq/ops/pauli_string_test.py +49 -6
  198. cirq/ops/pauli_sum_exponential.py +2 -1
  199. cirq/ops/permutation_gate.py +2 -1
  200. cirq/ops/phased_iswap_gate.py +3 -2
  201. cirq/ops/phased_x_gate.py +5 -4
  202. cirq/ops/phased_x_z_gate.py +12 -5
  203. cirq/ops/projector.py +2 -1
  204. cirq/ops/qubit_manager.py +2 -1
  205. cirq/ops/qubit_order.py +2 -1
  206. cirq/ops/qubit_order_or_list.py +1 -1
  207. cirq/ops/random_gate_channel.py +3 -2
  208. cirq/ops/raw_types.py +33 -16
  209. cirq/ops/raw_types_test.py +4 -3
  210. cirq/ops/state_preparation_channel.py +2 -1
  211. cirq/ops/three_qubit_gates.py +3 -2
  212. cirq/ops/two_qubit_diagonal_gate.py +3 -2
  213. cirq/ops/uniform_superposition_gate.py +2 -1
  214. cirq/ops/wait_gate.py +10 -4
  215. cirq/protocols/act_on_protocol.py +2 -1
  216. cirq/protocols/act_on_protocol_test.py +2 -1
  217. cirq/protocols/apply_channel_protocol.py +2 -1
  218. cirq/protocols/apply_mixture_protocol.py +2 -1
  219. cirq/protocols/apply_mixture_protocol_test.py +2 -1
  220. cirq/protocols/apply_unitary_protocol.py +2 -1
  221. cirq/protocols/apply_unitary_protocol_test.py +2 -0
  222. cirq/protocols/approximate_equality_protocol.py +2 -1
  223. cirq/protocols/circuit_diagram_info_protocol.py +2 -1
  224. cirq/protocols/control_key_protocol.py +7 -0
  225. cirq/protocols/decompose_protocol.py +2 -12
  226. cirq/protocols/has_stabilizer_effect_protocol.py +1 -1
  227. cirq/protocols/has_stabilizer_effect_protocol_test.py +11 -9
  228. cirq/protocols/has_unitary_protocol_test.py +3 -3
  229. cirq/protocols/hash_from_pickle_test.py +2 -2
  230. cirq/protocols/inverse_protocol.py +2 -1
  231. cirq/protocols/json_serialization.py +5 -4
  232. cirq/protocols/json_serialization_test.py +31 -31
  233. cirq/protocols/kraus_protocol.py +4 -3
  234. cirq/protocols/kraus_protocol_test.py +7 -7
  235. cirq/protocols/measurement_key_protocol.py +32 -8
  236. cirq/protocols/mixture_protocol.py +3 -2
  237. cirq/protocols/mixture_protocol_test.py +7 -7
  238. cirq/protocols/mul_protocol_test.py +4 -4
  239. cirq/protocols/phase_protocol.py +13 -4
  240. cirq/protocols/pow_protocol.py +2 -1
  241. cirq/protocols/pow_protocol_test.py +5 -5
  242. cirq/protocols/qasm.py +2 -1
  243. cirq/protocols/qid_shape_protocol.py +2 -1
  244. cirq/protocols/resolve_parameters.py +17 -15
  245. cirq/protocols/trace_distance_bound.py +2 -1
  246. cirq/protocols/unitary_protocol.py +21 -21
  247. cirq/protocols/unitary_protocol_test.py +31 -19
  248. cirq/qis/channels.py +1 -1
  249. cirq/qis/channels_test.py +1 -1
  250. cirq/qis/clifford_tableau.py +16 -15
  251. cirq/qis/clifford_tableau_test.py +17 -17
  252. cirq/qis/entropy.py +3 -3
  253. cirq/qis/entropy_test.py +1 -1
  254. cirq/qis/quantum_state_representation.py +2 -1
  255. cirq/qis/states.py +7 -2
  256. cirq/qis/states_test.py +54 -54
  257. cirq/sim/classical_simulator.py +25 -14
  258. cirq/sim/classical_simulator_test.py +85 -30
  259. cirq/sim/clifford/clifford_simulator.py +7 -6
  260. cirq/sim/clifford/clifford_simulator_test.py +51 -50
  261. cirq/sim/clifford/clifford_tableau_simulation_state.py +2 -1
  262. cirq/sim/clifford/stabilizer_ch_form_simulation_state.py +2 -1
  263. cirq/sim/clifford/stabilizer_sampler.py +1 -1
  264. cirq/sim/clifford/stabilizer_simulation_state.py +2 -1
  265. cirq/sim/clifford/stabilizer_state_ch_form.py +16 -15
  266. cirq/sim/clifford/stabilizer_state_ch_form_test.py +0 -1
  267. cirq/sim/density_matrix_simulation_state.py +7 -6
  268. cirq/sim/density_matrix_simulator.py +3 -2
  269. cirq/sim/density_matrix_simulator_test.py +94 -84
  270. cirq/sim/density_matrix_utils.py +2 -1
  271. cirq/sim/density_matrix_utils_test.py +1 -1
  272. cirq/sim/mux.py +35 -8
  273. cirq/sim/mux_test.py +39 -26
  274. cirq/sim/simulation_product_state.py +2 -1
  275. cirq/sim/simulation_product_state_test.py +8 -7
  276. cirq/sim/simulation_state.py +6 -5
  277. cirq/sim/simulation_state_base.py +3 -2
  278. cirq/sim/simulation_state_test.py +7 -6
  279. cirq/sim/simulation_utils.py +2 -1
  280. cirq/sim/simulator.py +4 -3
  281. cirq/sim/simulator_base.py +2 -1
  282. cirq/sim/simulator_base_test.py +51 -36
  283. cirq/sim/simulator_test.py +41 -36
  284. cirq/sim/sparse_simulator.py +3 -2
  285. cirq/sim/sparse_simulator_test.py +92 -82
  286. cirq/sim/state_vector.py +5 -6
  287. cirq/sim/state_vector_simulation_state.py +10 -9
  288. cirq/sim/state_vector_simulator.py +2 -1
  289. cirq/sim/state_vector_simulator_test.py +9 -9
  290. cirq/sim/state_vector_test.py +40 -39
  291. cirq/study/__init__.py +1 -0
  292. cirq/study/flatten_expressions.py +2 -1
  293. cirq/study/resolver.py +31 -18
  294. cirq/study/resolver_test.py +1 -1
  295. cirq/study/result.py +2 -1
  296. cirq/study/result_test.py +20 -20
  297. cirq/study/sweepable.py +2 -1
  298. cirq/study/sweepable_test.py +20 -20
  299. cirq/study/sweeps.py +26 -1
  300. cirq/study/sweeps_test.py +67 -43
  301. cirq/testing/_compat_test_data/__init__.py +3 -3
  302. cirq/testing/circuit_compare.py +2 -1
  303. cirq/testing/circuit_compare_test.py +16 -14
  304. cirq/testing/consistent_act_on_test.py +1 -1
  305. cirq/testing/consistent_channels.py +2 -2
  306. cirq/testing/consistent_controlled_gate_op.py +2 -2
  307. cirq/testing/consistent_controlled_gate_op_test.py +2 -1
  308. cirq/testing/consistent_decomposition.py +4 -2
  309. cirq/testing/consistent_phase_by.py +1 -1
  310. cirq/testing/consistent_protocols.py +2 -1
  311. cirq/testing/consistent_protocols_test.py +3 -3
  312. cirq/testing/consistent_qasm.py +4 -3
  313. cirq/testing/consistent_qasm_test.py +3 -3
  314. cirq/testing/consistent_resolve_parameters.py +1 -1
  315. cirq/testing/consistent_unitary.py +1 -1
  316. cirq/testing/consistent_unitary_test.py +1 -1
  317. cirq/testing/deprecation.py +1 -1
  318. cirq/testing/devices.py +3 -2
  319. cirq/testing/equals_tester.py +4 -3
  320. cirq/testing/equivalent_basis_map.py +4 -2
  321. cirq/testing/json.py +3 -2
  322. cirq/testing/lin_alg_utils.py +1 -1
  323. cirq/testing/logs.py +1 -1
  324. cirq/testing/op_tree.py +1 -1
  325. cirq/testing/order_tester.py +2 -2
  326. cirq/testing/pytest_utils.py +2 -1
  327. cirq/testing/random_circuit.py +2 -1
  328. cirq/testing/random_circuit_test.py +2 -1
  329. cirq/testing/repr_pretty_tester.py +3 -3
  330. cirq/transformers/__init__.py +1 -0
  331. cirq/transformers/_connected_component.py +231 -0
  332. cirq/transformers/_connected_component_test.py +200 -0
  333. cirq/transformers/align_test.py +13 -13
  334. cirq/transformers/analytical_decompositions/clifford_decomposition.py +8 -7
  335. cirq/transformers/analytical_decompositions/clifford_decomposition_test.py +5 -5
  336. cirq/transformers/analytical_decompositions/controlled_gate_decomposition.py +11 -10
  337. cirq/transformers/analytical_decompositions/controlled_gate_decomposition_test.py +6 -6
  338. cirq/transformers/analytical_decompositions/cphase_to_fsim.py +3 -2
  339. cirq/transformers/analytical_decompositions/cphase_to_fsim_test.py +11 -10
  340. cirq/transformers/analytical_decompositions/quantum_shannon_decomposition.py +8 -7
  341. cirq/transformers/analytical_decompositions/quantum_shannon_decomposition_test.py +17 -20
  342. cirq/transformers/analytical_decompositions/single_qubit_decompositions_test.py +33 -27
  343. cirq/transformers/analytical_decompositions/single_to_two_qubit_isometry_test.py +1 -1
  344. cirq/transformers/analytical_decompositions/three_qubit_decomposition.py +1 -1
  345. cirq/transformers/analytical_decompositions/two_qubit_state_preparation_test.py +12 -11
  346. cirq/transformers/analytical_decompositions/two_qubit_to_cz.py +5 -2
  347. cirq/transformers/analytical_decompositions/two_qubit_to_cz_test.py +3 -3
  348. cirq/transformers/analytical_decompositions/two_qubit_to_fsim.py +2 -1
  349. cirq/transformers/analytical_decompositions/two_qubit_to_ms.py +2 -1
  350. cirq/transformers/analytical_decompositions/two_qubit_to_ms_test.py +2 -2
  351. cirq/transformers/analytical_decompositions/two_qubit_to_sqrt_iswap.py +2 -1
  352. cirq/transformers/analytical_decompositions/two_qubit_to_sqrt_iswap_test.py +32 -30
  353. cirq/transformers/drop_negligible_operations_test.py +7 -7
  354. cirq/transformers/dynamical_decoupling.py +185 -112
  355. cirq/transformers/dynamical_decoupling_test.py +195 -201
  356. cirq/transformers/eject_phased_paulis.py +2 -1
  357. cirq/transformers/eject_phased_paulis_test.py +3 -2
  358. cirq/transformers/eject_z.py +5 -3
  359. cirq/transformers/eject_z_test.py +23 -25
  360. cirq/transformers/expand_composite.py +3 -2
  361. cirq/transformers/expand_composite_test.py +14 -14
  362. cirq/transformers/gauge_compiling/__init__.py +13 -0
  363. cirq/transformers/gauge_compiling/gauge_compiling.py +3 -2
  364. cirq/transformers/gauge_compiling/gauge_compiling_test.py +14 -12
  365. cirq/transformers/gauge_compiling/gauge_compiling_test_utils.py +3 -3
  366. cirq/transformers/gauge_compiling/idle_moments_gauge.py +225 -0
  367. cirq/transformers/gauge_compiling/idle_moments_gauge_test.py +193 -0
  368. cirq/transformers/gauge_compiling/multi_moment_cphase_gauge.py +242 -0
  369. cirq/transformers/gauge_compiling/multi_moment_cphase_gauge_test.py +243 -0
  370. cirq/transformers/gauge_compiling/multi_moment_gauge_compiling.py +151 -0
  371. cirq/transformers/gauge_compiling/sqrt_cz_gauge.py +2 -1
  372. cirq/transformers/heuristic_decompositions/gate_tabulation_math_utils.py +1 -1
  373. cirq/transformers/heuristic_decompositions/gate_tabulation_math_utils_test.py +6 -6
  374. cirq/transformers/heuristic_decompositions/two_qubit_gate_tabulation.py +3 -2
  375. cirq/transformers/measurement_transformers.py +2 -1
  376. cirq/transformers/measurement_transformers_test.py +45 -39
  377. cirq/transformers/merge_k_qubit_gates.py +2 -1
  378. cirq/transformers/merge_k_qubit_gates_test.py +1 -1
  379. cirq/transformers/merge_single_qubit_gates.py +9 -5
  380. cirq/transformers/merge_single_qubit_gates_test.py +22 -22
  381. cirq/transformers/noise_adding_test.py +2 -2
  382. cirq/transformers/optimize_for_target_gateset.py +2 -1
  383. cirq/transformers/optimize_for_target_gateset_test.py +11 -9
  384. cirq/transformers/qubit_management_transformers_test.py +6 -2
  385. cirq/transformers/routing/mapping_manager.py +2 -1
  386. cirq/transformers/routing/route_circuit_cqc.py +2 -1
  387. cirq/transformers/stratify.py +2 -1
  388. cirq/transformers/symbolize.py +2 -1
  389. cirq/transformers/tag_transformers.py +2 -1
  390. cirq/transformers/target_gatesets/compilation_target_gateset.py +2 -1
  391. cirq/transformers/target_gatesets/cz_gateset.py +2 -1
  392. cirq/transformers/target_gatesets/cz_gateset_test.py +1 -1
  393. cirq/transformers/target_gatesets/sqrt_iswap_gateset.py +2 -1
  394. cirq/transformers/transformer_api.py +2 -1
  395. cirq/transformers/transformer_primitives.py +271 -145
  396. cirq/transformers/transformer_primitives_test.py +185 -1
  397. cirq/value/abc_alt.py +2 -1
  398. cirq/value/classical_data.py +2 -1
  399. cirq/value/condition.py +2 -1
  400. cirq/value/digits.py +9 -2
  401. cirq/value/duration.py +6 -5
  402. cirq/value/linear_dict.py +4 -9
  403. cirq/value/measurement_key.py +2 -1
  404. cirq/value/periodic_value.py +3 -2
  405. cirq/value/product_state.py +2 -1
  406. cirq/value/value_equality_attr.py +2 -1
  407. cirq/vis/density_matrix.py +1 -1
  408. cirq/vis/heatmap.py +2 -1
  409. cirq/vis/histogram.py +2 -1
  410. cirq/vis/state_histogram.py +2 -1
  411. cirq/work/collector.py +2 -1
  412. cirq/work/observable_grouping.py +2 -1
  413. cirq/work/observable_measurement.py +2 -1
  414. cirq/work/observable_measurement_data.py +2 -1
  415. cirq/work/observable_measurement_test.py +1 -1
  416. cirq/work/observable_readout_calibration.py +2 -1
  417. cirq/work/observable_readout_calibration_test.py +1 -1
  418. cirq/work/observable_settings.py +2 -1
  419. cirq/work/sampler.py +2 -1
  420. cirq/work/sampler_test.py +1 -1
  421. {cirq_core-1.7.0.dev20250825174419.dist-info → cirq_core-1.7.0.dev20251203004401.dist-info}/METADATA +5 -6
  422. {cirq_core-1.7.0.dev20250825174419.dist-info → cirq_core-1.7.0.dev20251203004401.dist-info}/RECORD +425 -406
  423. cirq/contrib/json_test.py +0 -33
  424. {cirq_core-1.7.0.dev20250825174419.dist-info → cirq_core-1.7.0.dev20251203004401.dist-info}/WHEEL +0 -0
  425. {cirq_core-1.7.0.dev20250825174419.dist-info → cirq_core-1.7.0.dev20251203004401.dist-info}/licenses/LICENSE +0 -0
  426. {cirq_core-1.7.0.dev20250825174419.dist-info → cirq_core-1.7.0.dev20251203004401.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,253 @@
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
+ import numpy as np
16
+ import pytest
17
+ import sympy
18
+
19
+ import cirq
20
+
21
+ # Import the class directly for testing
22
+ from cirq.contrib.quantikz.circuit_to_latex_quantikz import (
23
+ CircuitToQuantikz,
24
+ DEFAULT_PREAMBLE_TEMPLATE,
25
+ )
26
+
27
+
28
+ def test_empty_circuit_raises_value_error():
29
+ """Test that an empty circuit raises a ValueError."""
30
+ empty_circuit = cirq.Circuit()
31
+ with pytest.raises(ValueError, match="Input circuit cannot be empty."):
32
+ CircuitToQuantikz(empty_circuit)
33
+
34
+
35
+ def test_circuit_no_qubits_raises_value_error():
36
+ """Test that a circuit with no qubits raises a ValueError."""
37
+ empty_circuit = cirq.Circuit(cirq.global_phase_operation(-1))
38
+ with pytest.raises(ValueError, match="Circuit contains no qubits."):
39
+ CircuitToQuantikz(empty_circuit)
40
+
41
+
42
+ def test_basic_circuit_conversion():
43
+ """Test a simple circuit conversion to LaTeX."""
44
+ q0, q1, q2, q3 = cirq.LineQubit.range(4)
45
+ circuit = cirq.Circuit(
46
+ cirq.H(q0),
47
+ cirq.PhasedXZGate(x_exponent=1, z_exponent=1, axis_phase_exponent=0.5)(q0),
48
+ cirq.CZ(q0, q1),
49
+ cirq.CNOT(q0, q1),
50
+ cirq.SWAP(q0, q1),
51
+ cirq.Moment(cirq.CZ(q1, q2), cirq.CZ(q0, q3)),
52
+ cirq.FSimGate(np.pi / 2, np.pi / 6)(q0, q1),
53
+ cirq.measure(q0, q2, q3, key='m0'),
54
+ )
55
+ converter = CircuitToQuantikz(circuit, wire_labels="q")
56
+ latex_code = converter.generate_latex_document()
57
+
58
+ assert r"\lstick{$q_{0}$} & \gate[" in latex_code
59
+ assert r"& \meter[" in latex_code
60
+ assert "\\begin{quantikz}" in latex_code
61
+ assert "\\end{quantikz}" in latex_code
62
+ assert DEFAULT_PREAMBLE_TEMPLATE.strip() in latex_code.strip()
63
+
64
+
65
+ def test_parameter_display():
66
+ """Test that gate parameters are correctly displayed or hidden."""
67
+
68
+ q_param = cirq.LineQubit(0)
69
+ alpha = sympy.Symbol("\\alpha") # Parameter symbol
70
+ beta = sympy.Symbol("\\beta") # Parameter symbol
71
+ param_circuit = cirq.Circuit(
72
+ cirq.H(q_param),
73
+ cirq.rz(alpha).on(q_param), # Parameterized gate
74
+ cirq.X(q_param),
75
+ cirq.X(q_param) ** sympy.Symbol("a_2"),
76
+ cirq.X(q_param) ** 2.0,
77
+ cirq.X(q_param) ** sympy.N(1.5),
78
+ cirq.X(q_param) ** sympy.N(3.0),
79
+ cirq.Y(q_param) ** 0.25, # Parameterized exponent
80
+ cirq.Y(q_param) ** alpha, # Formula exponent
81
+ cirq.X(q_param), # Parameterized exponent
82
+ cirq.rx(beta).on(q_param), # Parameterized gate
83
+ cirq.H(q_param),
84
+ cirq.CZPowGate(exponent=0.25).on(q_param, cirq.q(2)),
85
+ cirq.measure(q_param, key="result"),
86
+ )
87
+ # Test with show_parameters=True (default)
88
+ converter_show_params = CircuitToQuantikz(param_circuit, show_parameters=True)
89
+ latex_show_params = converter_show_params.generate_latex_document()
90
+ assert r"R_{Z}(\alpha)" in latex_show_params
91
+ assert r"Y^{0.25}" in latex_show_params
92
+ assert r"H" in latex_show_params
93
+ # Test with show_parameters=False
94
+ converter_show_params = CircuitToQuantikz(param_circuit, show_parameters=False)
95
+ latex_show_params = converter_show_params.generate_latex_document()
96
+ assert r"R_{Z}(\alpha)" not in latex_show_params
97
+ assert r"Y^{0.25}" not in latex_show_params
98
+ assert r"H" in latex_show_params
99
+ # Test with folding
100
+ converter_show_params = CircuitToQuantikz(param_circuit, show_parameters=True, fold_at=5)
101
+ latex_show_params = converter_show_params.generate_latex_document()
102
+ assert r"R_{Z}(\alpha)" in latex_show_params
103
+ assert r"Y^{0.25}" in latex_show_params
104
+ assert r"H" in latex_show_params
105
+
106
+ # Test with folding at boundary
107
+ converter_show_params = CircuitToQuantikz(param_circuit, show_parameters=True, fold_at=1)
108
+ latex_show_params = converter_show_params.generate_latex_document()
109
+ assert r"R_{Z}(\alpha)" in latex_show_params
110
+ assert r"Y^{0.25}" in latex_show_params
111
+ assert r"H" in latex_show_params
112
+
113
+
114
+ def test_custom_gate_name_map():
115
+ """Test custom gate name mapping."""
116
+ q = cirq.LineQubit(0)
117
+ circuit = cirq.Circuit(cirq.H(q), cirq.X(q))
118
+ custom_map = {"H": "Hadamard"}
119
+ converter = CircuitToQuantikz(circuit, gate_name_map=custom_map)
120
+ latex_code = converter.generate_latex_document()
121
+
122
+ assert r"Hadamard}" in latex_code
123
+ assert r"{H}" not in latex_code # Ensure original H is not there
124
+
125
+
126
+ def test_wire_labels():
127
+ """Test different wire labeling options."""
128
+ q0, q1, q2 = cirq.NamedQubit('alice'), cirq.LineQubit(10), cirq.GridQubit(4, 3)
129
+ circuit = cirq.Circuit(cirq.H(q0), cirq.X(q1), cirq.Z(q2))
130
+
131
+ # 'q' labels
132
+ converter_q = CircuitToQuantikz(circuit, wire_labels="q")
133
+ latex_q = converter_q.generate_latex_document()
134
+ assert r"\lstick{$q_{0}$}" in latex_q
135
+ assert r"\lstick{$q_{1}$}" in latex_q
136
+ assert r"\lstick{$q_{2}$}" in latex_q
137
+
138
+ # 'index' labels
139
+ converter_idx = CircuitToQuantikz(circuit, wire_labels="index")
140
+ latex_idx = converter_idx.generate_latex_document()
141
+ assert r"\lstick{$0$}" in latex_idx
142
+ assert r"\lstick{$1$}" in latex_idx
143
+ assert r"\lstick{$2$}" in latex_idx
144
+
145
+ # 'qid' labels
146
+ converter_q = CircuitToQuantikz(circuit, wire_labels="qid")
147
+ latex_q = converter_q.generate_latex_document()
148
+ assert r"\lstick{$alice$}" in latex_q
149
+ assert r"\lstick{$q(10)$}" in latex_q
150
+ assert r"\lstick{$q(4, 3)$}" in latex_q
151
+
152
+
153
+ def test_custom_preamble_and_postamble():
154
+ """Test custom preamble and postamble injection."""
155
+ q = cirq.LineQubit(0)
156
+ circuit = cirq.Circuit(cirq.H(q))
157
+ custom_preamble_text = r"\usepackage{mycustompackage}"
158
+ custom_postamble_text = r"\end{tikzpicture}"
159
+
160
+ converter = CircuitToQuantikz(
161
+ circuit, custom_preamble=custom_preamble_text, custom_postamble=custom_postamble_text
162
+ )
163
+ latex_code = converter.generate_latex_document()
164
+
165
+ assert custom_preamble_text in latex_code
166
+ assert custom_postamble_text in latex_code
167
+ assert "% --- Custom Preamble Injection Point ---" in latex_code
168
+ assert "% --- Custom Postamble Start ---" in latex_code
169
+
170
+
171
+ def test_quantikz_options():
172
+ """Test global quantikz options."""
173
+ q = cirq.LineQubit(0)
174
+ circuit = cirq.Circuit(cirq.H(q))
175
+ options = "column sep=1em, row sep=0.5em"
176
+ converter = CircuitToQuantikz(circuit, quantikz_options=options)
177
+ latex_code = converter.generate_latex_document()
178
+
179
+ assert f"\\begin{{quantikz}}[{options}]" in latex_code
180
+
181
+
182
+ def test_float_precision_exponents():
183
+ """Test formatting of floating-point exponents."""
184
+ q = cirq.LineQubit(0)
185
+ circuit = cirq.Circuit(cirq.X(q) ** 0.12345, cirq.Y(q) ** 0.5)
186
+ converter = CircuitToQuantikz(circuit, float_precision_exps=3)
187
+ latex_code = converter.generate_latex_document()
188
+ assert r"X^{0.123}" in latex_code
189
+ assert r"Y^{0.5}" in latex_code # Should still be 0.5, not 0.500
190
+
191
+ converter_int_exp = CircuitToQuantikz(circuit, float_precision_exps=0)
192
+ latex_int_exp = converter_int_exp.generate_latex_document()
193
+ assert r"X^{0.0}" in latex_int_exp # 0.12345 rounded to 0
194
+ assert r"Y^{0.0}" in latex_int_exp # 0.5 is still 0.5 if not integer
195
+
196
+
197
+ def test_qubit_order():
198
+ qubits = cirq.LineQubit.range(4)
199
+ circuit = cirq.Circuit(cirq.X.on_each(*qubits))
200
+ qubit_order = cirq.QubitOrder.explicit([qubits[3], qubits[2], qubits[1], qubits[0]])
201
+ converter = CircuitToQuantikz(circuit, qubit_order=qubit_order)
202
+ latex_code = converter.generate_latex_document()
203
+ q3 = latex_code.find("q(3)")
204
+ q2 = latex_code.find("q(2)")
205
+ q1 = latex_code.find("q(1)")
206
+ q0 = latex_code.find("q(0)")
207
+ assert q3 != -1
208
+ assert q2 != -1
209
+ assert q1 != -1
210
+ assert q0 != -1
211
+ assert q3 < q2
212
+ assert q2 < q1
213
+ assert q1 < q0
214
+
215
+
216
+ @pytest.mark.parametrize("show_parameters", [True, False])
217
+ def test_custom_gate(show_parameters) -> None:
218
+ class CustomGate(cirq.Gate):
219
+ def __init__(self, exponent: float | int):
220
+ self.exponent = exponent
221
+
222
+ def _num_qubits_(self):
223
+ return 1
224
+
225
+ def __str__(self):
226
+ return f"Custom_Gate()**{self.exponent}"
227
+
228
+ def _unitary_(self):
229
+ raise NotImplementedError()
230
+
231
+ circuit = cirq.Circuit(
232
+ CustomGate(1.0).on(cirq.q(0)), CustomGate(1).on(cirq.q(0)), CustomGate(1.5).on(cirq.q(0))
233
+ )
234
+ converter = CircuitToQuantikz(circuit, show_parameters=show_parameters)
235
+ latex_code = converter.generate_latex_document()
236
+ assert "Custom" in latex_code
237
+
238
+
239
+ def test_misc_gates() -> None:
240
+ """Tests gates that have special handling."""
241
+ circuit = cirq.Circuit(cirq.global_phase_operation(-1), cirq.X(cirq.q(0)) ** 1.5)
242
+ converter = CircuitToQuantikz(circuit)
243
+ latex_code = converter.generate_latex_document()
244
+ assert latex_code
245
+
246
+
247
+ def test_classical_control() -> None:
248
+ circuit = cirq.Circuit(
249
+ cirq.measure(cirq.q(0), key='a'), cirq.X(cirq.q(1)).with_classical_controls('a')
250
+ )
251
+ converter = CircuitToQuantikz(circuit)
252
+ latex_code = converter.generate_latex_document()
253
+ assert "\\vcw" in latex_code
@@ -0,0 +1,424 @@
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"""Provides tools for rendering Cirq circuits as Quantikz LaTeX diagrams.
16
+
17
+ This module offers a high-level interface for converting `cirq.Circuit` objects
18
+ into visually appealing quantum circuit diagrams using the `quantikz` LaTeX package.
19
+ It extends the functionality of `CircuitToQuantikz` by handling the full rendering
20
+ pipeline: generating LaTeX, compiling it to PDF using `pdflatex`, and converting
21
+ the PDF to a PNG image using `pdftoppm`.
22
+
23
+ The primary function, `render_circuit`, streamlines this process, allowing users
24
+ to easily generate and optionally display circuit diagrams in environments like
25
+ Jupyter notebooks. It provides extensive customization options for the output
26
+ format, file paths, and rendering parameters, including direct control over
27
+ gate styling, circuit folding, and qubit labeling through arguments passed
28
+ to the underlying `CircuitToQuantikz` converter.
29
+
30
+ Note: the creation of PDF or PNG output is done by invoking external software
31
+ that must be installed separately on the user's system. The programs are
32
+ `pdflatex` (included in many TeX distributions) and `pdftoppm` (part of the
33
+ "poppler-utils" software package).
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import os
39
+ import pathlib
40
+ import shutil
41
+ import subprocess
42
+ import tempfile
43
+ import traceback
44
+ import warnings
45
+ from pathlib import Path
46
+ from typing import Any
47
+
48
+ from IPython import get_ipython
49
+ from IPython.display import display, Image
50
+
51
+ # Import individual Cirq packages as recommended for internal Cirq code
52
+ from cirq import circuits, ops
53
+
54
+ # Use absolute import for the sibling module
55
+ from cirq.contrib.quantikz.circuit_to_latex_quantikz import CircuitToQuantikz
56
+
57
+
58
+ # =============================================================================
59
+ # High-Level Wrapper Function
60
+ # =============================================================================
61
+ def render_circuit(
62
+ circuit: circuits.Circuit,
63
+ output_png_path: pathlib.Path | str | None = None,
64
+ output_pdf_path: pathlib.Path | str | None = None,
65
+ output_tex_path: pathlib.Path | str | None = None,
66
+ dpi: int = 300,
67
+ run_pdflatex: bool = True,
68
+ run_pdftoppm: bool = True,
69
+ display_png_jupyter: bool = True,
70
+ cleanup: bool = True,
71
+ debug: bool = False,
72
+ timeout: int = 120,
73
+ # Carried over CircuitToQuantikz args
74
+ gate_styles: dict[str, str] | None = None,
75
+ quantikz_options: str | None = None,
76
+ fold_at: int | None = None,
77
+ wire_labels: str = "qid",
78
+ show_parameters: bool = True,
79
+ gate_name_map: dict[str, str] | None = None,
80
+ float_precision_exps: int = 2,
81
+ qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
82
+ **kwargs: Any,
83
+ ) -> str | Image | None:
84
+ r"""Renders a Cirq circuit to a LaTeX diagram, compiles it, and optionally displays it.
85
+
86
+ This function takes a `cirq.Circuit` object, converts it into a Quantikz
87
+ LaTeX string, compiles the LaTeX into a PDF, and then converts the PDF
88
+ into a PNG image. It can optionally save these intermediate and final
89
+ files and display the PNG in a Jupyter environment.
90
+
91
+ Args:
92
+ circuit: The `cirq.Circuit` object to be rendered.
93
+ output_png_path: Optional path to save the generated PNG image. If
94
+ `None`, the PNG is only kept in a temporary directory (if
95
+ `cleanup` is `True`) or not generated if `run_pdftoppm` is `False`.
96
+ output_pdf_path: Optional path to save the generated PDF document.
97
+ output_tex_path: Optional path to save the generated LaTeX source file.
98
+ dpi: The DPI (dots per inch) for the output PNG image. Higher DPI
99
+ results in a larger and higher-resolution image.
100
+ run_pdflatex: If `True`, `pdflatex` is executed to compile the LaTeX
101
+ file into a PDF. Requires `pdflatex` to be installed and in PATH.
102
+ run_pdftoppm: If `True`, `pdftoppm` (from poppler-utils) is executed
103
+ to convert the PDF into a PNG image. Requires `pdftoppm` to be
104
+ installed and in PATH. This option is ignored if `run_pdflatex`
105
+ is `False`.
106
+ display_png_jupyter: If `True` and running in a Jupyter environment,
107
+ the generated PNG image will be displayed directly in the output
108
+ cell.
109
+ cleanup: If `True`, temporary files and directories created during
110
+ the process (LaTeX, log, aux, PDF, temporary PNGs) will be removed.
111
+ If `False`, they are kept for debugging.
112
+ debug: If `True`, prints additional debugging information to the console.
113
+ timeout: Maximum time in seconds to wait for `pdflatex` and `pdftoppm`
114
+ commands to complete.
115
+ gate_styles: An optional dictionary mapping gate names (strings) to
116
+ Quantikz style options (strings). These styles are applied to
117
+ the generated gates. If `None`, `GATE_STYLES_COLORFUL1` is used.
118
+ Passed to `CircuitToQuantikz`.
119
+ quantikz_options: An optional string of global options to pass to the
120
+ `quantikz` environment (e.g., `"[row sep=0.5em]"`). Passed to
121
+ `CircuitToQuantikz`.
122
+ fold_at: An optional integer specifying the number of moments after
123
+ which the circuit should be folded into a new line in the LaTeX
124
+ output. If `None`, the circuit is not folded. Passed to `CircuitToQuantikz`.
125
+ wire_labels: A string specifying how qubit wire labels should be
126
+ rendered. Passed to `CircuitToQuantikz`.
127
+ show_parameters: A boolean indicating whether gate parameters (e.g.,
128
+ exponents for `XPowGate`, angles for `Rx`) should be displayed
129
+ in the gate labels. Passed to `CircuitToQuantikz`.
130
+ gate_name_map: An optional dictionary mapping Cirq gate names (strings)
131
+ to custom LaTeX strings for rendering. This allows renaming gates
132
+ in the output. Passed to `CircuitToQuantikz`.
133
+ float_precision_exps: An integer specifying the number of decimal
134
+ places for formatting floating-point exponents. Passed to `CircuitToQuantikz`.
135
+ qubit_order: The order of the qubit lines in the rendered diagram.
136
+ **kwargs: Additional keyword arguments passed directly to the
137
+ `CircuitToQuantikz` constructor. Refer to `CircuitToQuantikz` for
138
+ available options. Note that explicit arguments in `render_circuit`
139
+ will override values provided via `**kwargs`.
140
+
141
+ Returns:
142
+ An `IPython.display.Image` object if `display_png_jupyter` is `True`
143
+ and running in a Jupyter environment, and the PNG was successfully
144
+ generated. Otherwise, returns the string path to the saved PNG if
145
+ `output_png_path` was provided and successful, or `None` if no PNG
146
+ was generated or displayed.
147
+
148
+ Raises:
149
+ warnings.warn: If `pdflatex` or `pdftoppm` executables are not found
150
+ when their respective `run_` flags are `True`.
151
+
152
+ Example:
153
+ >>> import cirq
154
+ >>> import numpy as np
155
+ >>> from cirq.contrib.quantikz import render_circuit
156
+ >>> q0, q1, q2 = cirq.LineQubit.range(3)
157
+ >>> circuit = cirq.Circuit(
158
+ ... cirq.H(q0),
159
+ ... cirq.CNOT(q0, q1),
160
+ ... cirq.rx(0.25*np.pi).on(q1),
161
+ ... cirq.measure(q0, q1, key='result')
162
+ ... )
163
+ >>> # Render and display in Jupyter (if available), also save to a file
164
+ >>> img_or_path = render_circuit(
165
+ ... circuit,
166
+ ... output_png_path="my_circuit.png",
167
+ ... fold_at=2,
168
+ ... wire_labels="qid",
169
+ ... quantikz_options="column sep=0.7em",
170
+ ... show_parameters=False # Example of new parameter
171
+ ... )
172
+ >>> # To view the saved PNG outside Jupyter:
173
+ >>> # import matplotlib.pyplot as plt
174
+ >>> # import matplotlib.image as mpimg
175
+ >>> # img = mpimg.imread('my_circuit.png')
176
+ >>> # plt.imshow(img)
177
+ >>> # plt.axis('off')
178
+ >>> # plt.show()
179
+ """
180
+
181
+ def _debug_print(*args: Any, **kwargs_print: Any) -> None:
182
+ if debug:
183
+ print("[Debug]", *args, **kwargs_print)
184
+
185
+ # Convert string paths to Path objects and resolve them
186
+ final_tex_path = Path(output_tex_path).expanduser().resolve() if output_tex_path else None
187
+ final_pdf_path = Path(output_pdf_path).expanduser().resolve() if output_pdf_path else None
188
+ final_png_path = Path(output_png_path).expanduser().resolve() if output_png_path else None
189
+
190
+ # Check for external tool availability
191
+ pdflatex_exec = shutil.which("pdflatex")
192
+ pdftoppm_exec = shutil.which("pdftoppm")
193
+
194
+ # Make the output PDF reproducible (independent of creation time)
195
+ env = dict(os.environ)
196
+ env.setdefault("SOURCE_DATE_EPOCH", "0")
197
+
198
+ if run_pdflatex and not pdflatex_exec: # pragma: no cover
199
+ warnings.warn(
200
+ "'pdflatex' not found. Cannot compile LaTeX. "
201
+ "Please install a LaTeX distribution (e.g., TeX Live, MiKTeX) "
202
+ "and ensure pdflatex is in your PATH. "
203
+ "On Ubuntu/Debian: `sudo apt-get install texlive-full` "
204
+ "(or `texlive-base` for minimal). "
205
+ "On macOS: `brew install --cask mactex` (or `brew install texlive` for minimal). "
206
+ "On Windows: Download and install MiKTeX or TeX Live."
207
+ )
208
+ # Disable dependent steps
209
+ run_pdflatex = run_pdftoppm = False
210
+ if run_pdftoppm and not pdftoppm_exec: # pragma: no cover
211
+ warnings.warn(
212
+ "'pdftoppm' not found. Cannot convert PDF to PNG. "
213
+ "This tool is part of the Poppler utilities. "
214
+ "On Ubuntu/Debian: `sudo apt-get install poppler-utils`. "
215
+ "On macOS: `brew install poppler`. "
216
+ "On Windows: Download Poppler for Windows "
217
+ "(e.g., from Poppler for Windows GitHub releases) "
218
+ "and add its `bin` directory to your system PATH."
219
+ )
220
+ # Disable dependent step
221
+ run_pdftoppm = False
222
+
223
+ # Use TemporaryDirectory for safe handling of temporary files
224
+ with tempfile.TemporaryDirectory() as tmpdir_s:
225
+ tmp_p = Path(tmpdir_s)
226
+ _debug_print(f"Temporary directory created at: {tmp_p}")
227
+ base_name = "circuit_render"
228
+ tmp_tex_path = tmp_p / f"{base_name}.tex"
229
+ tmp_pdf_path = tmp_p / f"{base_name}.pdf"
230
+ tmp_png_path = tmp_p / f"{base_name}.png" # Single PNG output from pdftoppm
231
+
232
+ # Prepare kwargs for CircuitToQuantikz, prioritizing explicit args
233
+ converter_kwargs = {
234
+ "gate_styles": gate_styles,
235
+ "quantikz_options": quantikz_options,
236
+ "fold_at": fold_at,
237
+ "wire_labels": wire_labels,
238
+ "show_parameters": show_parameters,
239
+ "gate_name_map": gate_name_map,
240
+ "float_precision_exps": float_precision_exps,
241
+ "qubit_order": qubit_order,
242
+ **kwargs, # Existing kwargs are merged, but explicit args take precedence
243
+ }
244
+
245
+ converter = CircuitToQuantikz(circuit, **converter_kwargs)
246
+
247
+ _debug_print("Generating LaTeX source...")
248
+ latex_s = converter.generate_latex_document()
249
+ _debug_print("Generated LaTeX (first 500 chars):\n", latex_s[:500] + "...")
250
+
251
+ tmp_tex_path.write_text(latex_s, encoding="utf-8")
252
+ _debug_print(f"LaTeX saved to temporary file: {tmp_tex_path}")
253
+
254
+ pdf_generated = False
255
+ if run_pdflatex and pdflatex_exec:
256
+ _debug_print(f"Running pdflatex ({pdflatex_exec})...")
257
+ # Run pdflatex twice for correct cross-references and layout
258
+ cmd_latex = [
259
+ pdflatex_exec,
260
+ "-interaction=nonstopmode", # Don't prompt for input
261
+ "-halt-on-error", # Exit on first error
262
+ "-output-directory",
263
+ str(tmp_p), # Output files to temp directory
264
+ str(tmp_tex_path),
265
+ ]
266
+ latex_failed = False
267
+ for i in range(2): # Run pdflatex twice
268
+ _debug_print(f" pdflatex run {i+1}/2...")
269
+ proc = subprocess.run(
270
+ cmd_latex,
271
+ capture_output=True,
272
+ text=True,
273
+ check=False, # Don't raise CalledProcessError immediately
274
+ cwd=tmp_p,
275
+ timeout=timeout,
276
+ env=env,
277
+ )
278
+ if proc.returncode != 0: # pragma: no cover
279
+ latex_failed = True
280
+ print(f"!!! pdflatex failed on run {i+1} (exit code {proc.returncode}) !!!")
281
+ log_file = tmp_tex_path.with_suffix(".log")
282
+ if log_file.exists():
283
+ print(
284
+ f"--- Tail of {log_file.name} ---\n"
285
+ f"{log_file.read_text(errors='ignore')[-2000:]}"
286
+ )
287
+ else:
288
+ if proc.stdout:
289
+ print(f"--- pdflatex stdout ---\n{proc.stdout[-2000:]}")
290
+ if proc.stderr:
291
+ print(f"--- pdflatex stderr ---\n{proc.stderr}")
292
+ break # Exit loop if pdflatex failed
293
+ elif not tmp_pdf_path.is_file() and i == 1: # pragma: no cover
294
+ latex_failed = True
295
+ print("!!! pdflatex completed, but PDF file not found. Check logs. !!!")
296
+ log_file = tmp_tex_path.with_suffix(".log")
297
+ if log_file.exists():
298
+ print(
299
+ f"--- Tail of {log_file.name} ---\n"
300
+ f"{log_file.read_text(errors='ignore')[-2000:]}"
301
+ )
302
+ break
303
+ elif tmp_pdf_path.is_file():
304
+ _debug_print(f" pdflatex run {i+1}/2 successful (PDF exists).")
305
+
306
+ if not latex_failed and tmp_pdf_path.is_file():
307
+ pdf_generated = True
308
+ _debug_print(f"PDF successfully generated at: {tmp_pdf_path}")
309
+ elif not latex_failed: # pragma: no cover
310
+ # pdflatex returned 0 but PDF not found
311
+ print("pdflatex reported success but PDF file was not found.")
312
+ if latex_failed: # pragma: no cover
313
+ return None # Critical failure, return None
314
+
315
+ png_generated, final_output_path_for_display = False, None
316
+ if run_pdftoppm and pdftoppm_exec and pdf_generated:
317
+ _debug_print(f"Running pdftoppm ({pdftoppm_exec})...")
318
+ # pdftoppm outputs to <prefix>-<page_number>.png if multiple pages,
319
+ # or <prefix>.png if single page with -singlefile.
320
+ # We expect a single page output here.
321
+ cmd_ppm = [
322
+ pdftoppm_exec,
323
+ "-png",
324
+ "-r",
325
+ str(dpi),
326
+ "-singlefile", # Ensures single output file for single-page PDFs
327
+ str(tmp_pdf_path),
328
+ str(tmp_p / base_name), # Output prefix for the PNG
329
+ ]
330
+ try:
331
+ proc = subprocess.run(
332
+ cmd_ppm,
333
+ capture_output=True,
334
+ text=True,
335
+ check=True, # Raise CalledProcessError for non-zero exit codes
336
+ cwd=tmp_p,
337
+ timeout=timeout,
338
+ env=env,
339
+ )
340
+ if tmp_png_path.is_file():
341
+ png_generated = True
342
+ _debug_print(f"PNG successfully generated at: {tmp_png_path}")
343
+ else:
344
+ print(
345
+ f"!!! pdftoppm succeeded but PNG ({tmp_png_path}) not found. !!!"
346
+ ) # pragma: no cover
347
+ except subprocess.CalledProcessError as e_ppm: # pragma: no cover
348
+ print(
349
+ f"!!! pdftoppm failed (exit code {e_ppm.returncode}) !!!\n"
350
+ f"Stdout: {e_ppm.stdout}\nStderr: {e_ppm.stderr}"
351
+ )
352
+ except subprocess.TimeoutExpired: # pragma: no cover
353
+ print("!!! pdftoppm timed out. !!!")
354
+ except Exception as e_ppm_other: # pragma: no cover
355
+ print(f"An unexpected error occurred during pdftoppm: {e_ppm_other}")
356
+
357
+ # Copy files to final destinations if requested
358
+ if final_tex_path and tmp_tex_path.exists():
359
+ final_tex_path.parent.mkdir(parents=True, exist_ok=True)
360
+ shutil.copy2(tmp_tex_path, final_tex_path)
361
+ _debug_print(f"Copied .tex to: {final_tex_path}")
362
+ if final_pdf_path and pdf_generated and tmp_pdf_path.exists():
363
+ final_pdf_path.parent.mkdir(parents=True, exist_ok=True)
364
+ shutil.copy2(tmp_pdf_path, final_pdf_path)
365
+ _debug_print(f"Copied .pdf to: {final_pdf_path}")
366
+ if final_png_path and png_generated and tmp_png_path.exists():
367
+ final_png_path.parent.mkdir(parents=True, exist_ok=True)
368
+ shutil.copy2(tmp_png_path, final_png_path)
369
+ _debug_print(f"Copied .png to: {final_png_path}")
370
+ final_output_path_for_display = final_png_path # Use the final path for display
371
+ elif png_generated and tmp_png_path.exists() and not final_png_path: # pragma: no cover
372
+ # If PNG was generated but no specific output_png_path,
373
+ # use the temp path for display
374
+ final_output_path_for_display = tmp_png_path
375
+
376
+ jupyter_image_object: Image | None = None
377
+
378
+ if (
379
+ display_png_jupyter
380
+ and final_output_path_for_display
381
+ and final_output_path_for_display.is_file()
382
+ ): # pragma: no cover
383
+ _debug_print(f"Attempting to display PNG in Jupyter: {final_output_path_for_display}")
384
+ try:
385
+ # Check if running in a Jupyter-like environment that supports display
386
+ # get_ipython() returns a shell object if in IPython, None otherwise.
387
+ # ZMQInteractiveShell is for Jupyter notebooks,
388
+ # TerminalInteractiveShell for IPython console.
389
+ sh_obj = get_ipython()
390
+ if sh_obj is not None and sh_obj.__class__.__name__ == "ZMQInteractiveShell":
391
+ current_image_obj = Image(filename=str(final_output_path_for_display))
392
+ display(current_image_obj)
393
+ jupyter_image_object = current_image_obj
394
+ _debug_print("PNG displayed in Jupyter notebook.")
395
+ else:
396
+ _debug_print(
397
+ "Not in a ZMQInteractiveShell (Jupyter notebook). "
398
+ "PNG not displayed inline."
399
+ )
400
+ # Still create Image object if it might be returned later
401
+ jupyter_image_object = Image(filename=str(final_output_path_for_display))
402
+ except Exception as e_disp:
403
+ print(f"Error displaying PNG in Jupyter: {e_disp}")
404
+ if debug:
405
+ traceback.print_exc()
406
+ jupyter_image_object = None
407
+ elif display_png_jupyter and (
408
+ not final_output_path_for_display or not final_output_path_for_display.is_file()
409
+ ): # pragma: no cover
410
+ if run_pdflatex and run_pdftoppm:
411
+ print("PNG display requested, but PNG not successfully created/found.")
412
+
413
+ # Determine return value based on requested outputs
414
+ if jupyter_image_object:
415
+ return jupyter_image_object
416
+ elif final_png_path and final_png_path.is_file(): # pragma: no cover
417
+ # Return path to saved PNG
418
+ return str(final_png_path)
419
+ elif output_tex_path and final_tex_path and final_tex_path.is_file(): # pragma: no cover
420
+ # If only LaTeX string was requested, read it back from the saved file
421
+ # This is a bit indirect, but aligns with returning a string path
422
+ return final_tex_path.read_text(encoding="utf-8")
423
+ # Default return if no specific output is generated or requested as return
424
+ return None # pragma: no cover