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.
- cirq/__init__.py +1 -0
- cirq/_compat.py +3 -2
- cirq/_compat_test.py +16 -15
- cirq/_doc.py +4 -3
- cirq/_import.py +2 -1
- cirq/_version.py +1 -1
- cirq/_version_test.py +1 -1
- cirq/circuits/_bucket_priority_queue.py +2 -1
- cirq/circuits/circuit.py +19 -17
- cirq/circuits/circuit_operation.py +2 -1
- cirq/circuits/circuit_operation_test.py +19 -0
- cirq/circuits/circuit_test.py +31 -12
- cirq/circuits/frozen_circuit.py +3 -2
- cirq/circuits/moment.py +3 -15
- cirq/circuits/optimization_pass.py +2 -1
- cirq/circuits/qasm_output.py +39 -10
- cirq/circuits/qasm_output_test.py +51 -2
- cirq/circuits/text_diagram_drawer.py +2 -1
- cirq/contrib/acquaintance/bipartite.py +2 -1
- cirq/contrib/acquaintance/devices.py +1 -1
- cirq/contrib/acquaintance/executor.py +4 -5
- cirq/contrib/acquaintance/executor_test.py +2 -1
- cirq/contrib/acquaintance/gates.py +2 -1
- cirq/contrib/acquaintance/gates_test.py +1 -1
- cirq/contrib/acquaintance/inspection_utils.py +2 -1
- cirq/contrib/acquaintance/mutation_utils.py +2 -1
- cirq/contrib/acquaintance/optimizers.py +2 -1
- cirq/contrib/acquaintance/permutation.py +2 -1
- cirq/contrib/acquaintance/permutation_test.py +1 -1
- cirq/contrib/acquaintance/shift.py +2 -1
- cirq/contrib/acquaintance/shift_swap_network.py +2 -1
- cirq/contrib/acquaintance/strategies/complete.py +3 -2
- cirq/contrib/acquaintance/strategies/cubic.py +2 -1
- cirq/contrib/acquaintance/strategies/quartic_paired.py +2 -1
- cirq/contrib/acquaintance/strategies/quartic_paired_test.py +1 -1
- cirq/contrib/acquaintance/testing.py +2 -1
- cirq/contrib/acquaintance/topological_sort.py +2 -1
- cirq/contrib/bayesian_network/bayesian_network_gate.py +3 -2
- cirq/contrib/circuitdag/circuit_dag.py +4 -2
- cirq/contrib/custom_simulators/custom_state_simulator.py +2 -1
- cirq/contrib/custom_simulators/custom_state_simulator_test.py +1 -1
- cirq/contrib/graph_device/graph_device.py +2 -1
- cirq/contrib/graph_device/graph_device_test.py +2 -1
- cirq/contrib/graph_device/hypergraph.py +2 -1
- cirq/contrib/graph_device/uniform_graph_device.py +2 -1
- cirq/contrib/json.py +14 -2
- cirq/contrib/json_test_data/BayesianNetworkGate.json +10 -0
- cirq/contrib/json_test_data/BayesianNetworkGate.repr +3 -0
- cirq/contrib/json_test_data/QuantumVolumeResult.json +169 -0
- cirq/contrib/json_test_data/QuantumVolumeResult.repr +22 -0
- cirq/contrib/json_test_data/SwapPermutationGate.json +3 -0
- cirq/contrib/json_test_data/SwapPermutationGate.repr +1 -0
- cirq/contrib/json_test_data/spec.py +0 -2
- cirq/contrib/noise_models/noise_models.py +2 -1
- cirq/contrib/paulistring/clifford_optimize.py +20 -2
- cirq/contrib/paulistring/optimize.py +1 -1
- cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation.py +146 -35
- cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation_test.py +81 -178
- cirq/contrib/paulistring/recombine.py +5 -2
- cirq/contrib/paulistring/separate.py +1 -1
- cirq/contrib/qasm_import/_lexer.py +6 -1
- cirq/contrib/qasm_import/_lexer_test.py +1 -1
- cirq/contrib/qasm_import/_parser.py +24 -8
- cirq/contrib/qasm_import/_parser_test.py +44 -6
- cirq/contrib/qcircuit/qcircuit_pdf_test.py +6 -9
- cirq/contrib/quantikz/__init__.py +21 -0
- cirq/contrib/quantikz/circuit_to_latex_quantikz.py +680 -0
- cirq/contrib/quantikz/circuit_to_latex_quantikz_test.py +253 -0
- cirq/contrib/quantikz/circuit_to_latex_render.py +424 -0
- cirq/contrib/quantikz/circuit_to_latex_render_test.py +44 -0
- cirq/contrib/quantum_volume/quantum_volume.py +2 -1
- cirq/contrib/quimb/density_matrix.py +1 -1
- cirq/contrib/quimb/grid_circuits.py +2 -1
- cirq/contrib/quimb/grid_circuits_test.py +1 -1
- cirq/contrib/quimb/mps_simulator.py +4 -3
- cirq/contrib/quimb/state_vector.py +2 -1
- cirq/contrib/quirk/export_to_quirk.py +2 -1
- cirq/contrib/quirk/linearize_circuit.py +1 -1
- cirq/contrib/quirk/quirk_gate.py +2 -1
- cirq/contrib/routing/device.py +1 -1
- cirq/contrib/routing/greedy.py +2 -1
- cirq/contrib/routing/initialization.py +2 -1
- cirq/contrib/routing/router.py +2 -1
- cirq/contrib/routing/swap_network.py +2 -1
- cirq/contrib/routing/utils.py +2 -1
- cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py +7 -5
- cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py +6 -6
- cirq/devices/device.py +2 -1
- cirq/devices/grid_device_metadata.py +2 -1
- cirq/devices/grid_qubit.py +7 -6
- cirq/devices/insertion_noise_model.py +2 -1
- cirq/devices/line_qubit.py +2 -1
- cirq/devices/named_topologies.py +2 -1
- cirq/devices/noise_model.py +2 -1
- cirq/devices/noise_model_test.py +1 -1
- cirq/devices/noise_properties.py +2 -1
- cirq/devices/superconducting_qubits_noise_properties_test.py +2 -1
- cirq/devices/thermal_noise_model.py +2 -1
- cirq/experiments/__init__.py +2 -0
- cirq/experiments/benchmarking/parallel_xeb.py +2 -1
- cirq/experiments/benchmarking/parallel_xeb_test.py +1 -1
- cirq/experiments/fidelity_estimation.py +2 -1
- cirq/experiments/fidelity_estimation_test.py +1 -1
- cirq/experiments/ghz_2d.py +150 -0
- cirq/experiments/ghz_2d_test.py +155 -0
- cirq/experiments/n_qubit_tomography.py +2 -1
- cirq/experiments/n_qubit_tomography_test.py +1 -1
- cirq/experiments/purity_estimation.py +1 -1
- cirq/experiments/qubit_characterizations.py +33 -4
- cirq/experiments/qubit_characterizations_test.py +16 -0
- cirq/experiments/random_quantum_circuit_generation.py +2 -1
- cirq/experiments/random_quantum_circuit_generation_test.py +2 -1
- cirq/experiments/readout_confusion_matrix.py +2 -1
- cirq/experiments/readout_confusion_matrix_test.py +1 -1
- cirq/experiments/single_qubit_readout_calibration.py +2 -1
- cirq/experiments/single_qubit_readout_calibration_test.py +1 -1
- cirq/experiments/t1_decay_experiment.py +2 -1
- cirq/experiments/two_qubit_xeb.py +2 -1
- cirq/experiments/two_qubit_xeb_test.py +1 -1
- cirq/experiments/xeb_fitting.py +2 -1
- cirq/experiments/xeb_fitting_test.py +1 -1
- cirq/experiments/xeb_sampling.py +5 -3
- cirq/experiments/xeb_sampling_test.py +1 -1
- cirq/experiments/xeb_simulation.py +2 -1
- cirq/experiments/xeb_simulation_test.py +2 -1
- cirq/experiments/z_phase_calibration.py +2 -1
- cirq/experiments/z_phase_calibration_test.py +18 -3
- cirq/interop/quirk/cells/__init__.py +1 -2
- cirq/interop/quirk/cells/all_cells.py +2 -1
- cirq/interop/quirk/cells/arithmetic_cells.py +2 -1
- cirq/interop/quirk/cells/cell.py +2 -1
- cirq/interop/quirk/cells/composite_cell.py +2 -1
- cirq/interop/quirk/cells/composite_cell_test.py +1 -1
- cirq/interop/quirk/cells/control_cells.py +2 -1
- cirq/interop/quirk/cells/frequency_space_cells.py +1 -1
- cirq/interop/quirk/cells/ignored_cells.py +1 -1
- cirq/interop/quirk/cells/input_cells.py +2 -1
- cirq/interop/quirk/cells/input_rotation_cells.py +2 -1
- cirq/interop/quirk/cells/measurement_cells.py +2 -1
- cirq/interop/quirk/cells/parse.py +2 -11
- cirq/interop/quirk/cells/qubit_permutation_cells.py +2 -1
- cirq/interop/quirk/cells/scalar_cells.py +2 -1
- cirq/interop/quirk/cells/single_qubit_rotation_cells.py +2 -1
- cirq/interop/quirk/cells/swap_cell.py +2 -1
- cirq/interop/quirk/cells/unsupported_cells.py +1 -1
- cirq/interop/quirk/url_to_circuit.py +2 -1
- cirq/json_resolver_cache.py +0 -2
- cirq/linalg/decompositions.py +6 -2
- cirq/linalg/decompositions_test.py +1 -0
- cirq/linalg/diagonalize.py +1 -1
- cirq/linalg/predicates.py +2 -1
- cirq/linalg/tolerance.py +2 -1
- cirq/linalg/transformations.py +3 -2
- cirq/ops/arithmetic_operation.py +4 -3
- cirq/ops/arithmetic_operation_test.py +1 -1
- cirq/ops/boolean_hamiltonian.py +4 -3
- cirq/ops/classically_controlled_operation.py +11 -11
- cirq/ops/classically_controlled_operation_test.py +26 -2
- cirq/ops/clifford_gate.py +3 -2
- cirq/ops/clifford_gate_test.py +1 -2
- cirq/ops/common_channels.py +2 -1
- cirq/ops/common_gates.py +3 -2
- cirq/ops/control_values.py +2 -1
- cirq/ops/controlled_gate.py +3 -2
- cirq/ops/controlled_gate_test.py +2 -1
- cirq/ops/controlled_operation.py +3 -2
- cirq/ops/controlled_operation_test.py +2 -1
- cirq/ops/dense_pauli_string.py +44 -81
- cirq/ops/dense_pauli_string_test.py +21 -0
- cirq/ops/diagonal_gate.py +3 -2
- cirq/ops/eigen_gate.py +9 -7
- cirq/ops/fourier_transform.py +3 -2
- cirq/ops/fourier_transform_test.py +2 -4
- cirq/ops/fsim_gate.py +3 -2
- cirq/ops/gate_operation.py +23 -12
- cirq/ops/gateset.py +22 -2
- cirq/ops/global_phase_op.py +3 -2
- cirq/ops/greedy_qubit_manager.py +2 -1
- cirq/ops/identity.py +2 -1
- cirq/ops/kraus_channel.py +2 -1
- cirq/ops/linear_combinations.py +12 -17
- cirq/ops/linear_combinations_test.py +23 -1
- cirq/ops/matrix_gates.py +2 -1
- cirq/ops/measure_util.py +8 -6
- cirq/ops/measurement_gate.py +2 -1
- cirq/ops/mixed_unitary_channel.py +2 -1
- cirq/ops/named_qubit.py +2 -2
- cirq/ops/op_tree.py +2 -1
- cirq/ops/parallel_gate.py +3 -2
- cirq/ops/parity_gates.py +2 -1
- cirq/ops/parity_gates_test.py +35 -0
- cirq/ops/pauli_interaction_gate.py +2 -1
- cirq/ops/pauli_measurement_gate.py +2 -1
- cirq/ops/pauli_string.py +37 -57
- cirq/ops/pauli_string_phasor.py +6 -5
- cirq/ops/pauli_string_raw_types.py +2 -1
- cirq/ops/pauli_string_test.py +49 -6
- cirq/ops/pauli_sum_exponential.py +2 -1
- cirq/ops/permutation_gate.py +2 -1
- cirq/ops/phased_iswap_gate.py +3 -2
- cirq/ops/phased_x_gate.py +5 -4
- cirq/ops/phased_x_z_gate.py +12 -5
- cirq/ops/projector.py +2 -1
- cirq/ops/qubit_manager.py +2 -1
- cirq/ops/qubit_order.py +2 -1
- cirq/ops/qubit_order_or_list.py +1 -1
- cirq/ops/random_gate_channel.py +3 -2
- cirq/ops/raw_types.py +33 -16
- cirq/ops/raw_types_test.py +4 -3
- cirq/ops/state_preparation_channel.py +2 -1
- cirq/ops/three_qubit_gates.py +3 -2
- cirq/ops/two_qubit_diagonal_gate.py +3 -2
- cirq/ops/uniform_superposition_gate.py +2 -1
- cirq/ops/wait_gate.py +10 -4
- cirq/protocols/act_on_protocol.py +2 -1
- cirq/protocols/act_on_protocol_test.py +2 -1
- cirq/protocols/apply_channel_protocol.py +2 -1
- cirq/protocols/apply_mixture_protocol.py +2 -1
- cirq/protocols/apply_mixture_protocol_test.py +2 -1
- cirq/protocols/apply_unitary_protocol.py +2 -1
- cirq/protocols/apply_unitary_protocol_test.py +2 -0
- cirq/protocols/approximate_equality_protocol.py +2 -1
- cirq/protocols/circuit_diagram_info_protocol.py +2 -1
- cirq/protocols/control_key_protocol.py +7 -0
- cirq/protocols/decompose_protocol.py +2 -12
- cirq/protocols/has_stabilizer_effect_protocol.py +1 -1
- cirq/protocols/has_stabilizer_effect_protocol_test.py +11 -9
- cirq/protocols/has_unitary_protocol_test.py +3 -3
- cirq/protocols/hash_from_pickle_test.py +2 -2
- cirq/protocols/inverse_protocol.py +2 -1
- cirq/protocols/json_serialization.py +5 -4
- cirq/protocols/json_serialization_test.py +31 -31
- cirq/protocols/kraus_protocol.py +4 -3
- cirq/protocols/kraus_protocol_test.py +7 -7
- cirq/protocols/measurement_key_protocol.py +32 -8
- cirq/protocols/mixture_protocol.py +3 -2
- cirq/protocols/mixture_protocol_test.py +7 -7
- cirq/protocols/mul_protocol_test.py +4 -4
- cirq/protocols/phase_protocol.py +13 -4
- cirq/protocols/pow_protocol.py +2 -1
- cirq/protocols/pow_protocol_test.py +5 -5
- cirq/protocols/qasm.py +2 -1
- cirq/protocols/qid_shape_protocol.py +2 -1
- cirq/protocols/resolve_parameters.py +17 -15
- cirq/protocols/trace_distance_bound.py +2 -1
- cirq/protocols/unitary_protocol.py +21 -21
- cirq/protocols/unitary_protocol_test.py +31 -19
- cirq/qis/channels.py +1 -1
- cirq/qis/channels_test.py +1 -1
- cirq/qis/clifford_tableau.py +16 -15
- cirq/qis/clifford_tableau_test.py +17 -17
- cirq/qis/entropy.py +3 -3
- cirq/qis/entropy_test.py +1 -1
- cirq/qis/quantum_state_representation.py +2 -1
- cirq/qis/states.py +7 -2
- cirq/qis/states_test.py +54 -54
- cirq/sim/classical_simulator.py +25 -14
- cirq/sim/classical_simulator_test.py +85 -30
- cirq/sim/clifford/clifford_simulator.py +7 -6
- cirq/sim/clifford/clifford_simulator_test.py +51 -50
- cirq/sim/clifford/clifford_tableau_simulation_state.py +2 -1
- cirq/sim/clifford/stabilizer_ch_form_simulation_state.py +2 -1
- cirq/sim/clifford/stabilizer_sampler.py +1 -1
- cirq/sim/clifford/stabilizer_simulation_state.py +2 -1
- cirq/sim/clifford/stabilizer_state_ch_form.py +16 -15
- cirq/sim/clifford/stabilizer_state_ch_form_test.py +0 -1
- cirq/sim/density_matrix_simulation_state.py +7 -6
- cirq/sim/density_matrix_simulator.py +3 -2
- cirq/sim/density_matrix_simulator_test.py +94 -84
- cirq/sim/density_matrix_utils.py +2 -1
- cirq/sim/density_matrix_utils_test.py +1 -1
- cirq/sim/mux.py +35 -8
- cirq/sim/mux_test.py +39 -26
- cirq/sim/simulation_product_state.py +2 -1
- cirq/sim/simulation_product_state_test.py +8 -7
- cirq/sim/simulation_state.py +6 -5
- cirq/sim/simulation_state_base.py +3 -2
- cirq/sim/simulation_state_test.py +7 -6
- cirq/sim/simulation_utils.py +2 -1
- cirq/sim/simulator.py +4 -3
- cirq/sim/simulator_base.py +2 -1
- cirq/sim/simulator_base_test.py +51 -36
- cirq/sim/simulator_test.py +41 -36
- cirq/sim/sparse_simulator.py +3 -2
- cirq/sim/sparse_simulator_test.py +92 -82
- cirq/sim/state_vector.py +5 -6
- cirq/sim/state_vector_simulation_state.py +10 -9
- cirq/sim/state_vector_simulator.py +2 -1
- cirq/sim/state_vector_simulator_test.py +9 -9
- cirq/sim/state_vector_test.py +40 -39
- cirq/study/__init__.py +1 -0
- cirq/study/flatten_expressions.py +2 -1
- cirq/study/resolver.py +31 -18
- cirq/study/resolver_test.py +1 -1
- cirq/study/result.py +2 -1
- cirq/study/result_test.py +20 -20
- cirq/study/sweepable.py +2 -1
- cirq/study/sweepable_test.py +20 -20
- cirq/study/sweeps.py +26 -1
- cirq/study/sweeps_test.py +67 -43
- cirq/testing/_compat_test_data/__init__.py +3 -3
- cirq/testing/circuit_compare.py +2 -1
- cirq/testing/circuit_compare_test.py +16 -14
- cirq/testing/consistent_act_on_test.py +1 -1
- cirq/testing/consistent_channels.py +2 -2
- cirq/testing/consistent_controlled_gate_op.py +2 -2
- cirq/testing/consistent_controlled_gate_op_test.py +2 -1
- cirq/testing/consistent_decomposition.py +4 -2
- cirq/testing/consistent_phase_by.py +1 -1
- cirq/testing/consistent_protocols.py +2 -1
- cirq/testing/consistent_protocols_test.py +3 -3
- cirq/testing/consistent_qasm.py +4 -3
- cirq/testing/consistent_qasm_test.py +3 -3
- cirq/testing/consistent_resolve_parameters.py +1 -1
- cirq/testing/consistent_unitary.py +1 -1
- cirq/testing/consistent_unitary_test.py +1 -1
- cirq/testing/deprecation.py +1 -1
- cirq/testing/devices.py +3 -2
- cirq/testing/equals_tester.py +4 -3
- cirq/testing/equivalent_basis_map.py +4 -2
- cirq/testing/json.py +3 -2
- cirq/testing/lin_alg_utils.py +1 -1
- cirq/testing/logs.py +1 -1
- cirq/testing/op_tree.py +1 -1
- cirq/testing/order_tester.py +2 -2
- cirq/testing/pytest_utils.py +2 -1
- cirq/testing/random_circuit.py +2 -1
- cirq/testing/random_circuit_test.py +2 -1
- cirq/testing/repr_pretty_tester.py +3 -3
- cirq/transformers/__init__.py +1 -0
- cirq/transformers/_connected_component.py +231 -0
- cirq/transformers/_connected_component_test.py +200 -0
- cirq/transformers/align_test.py +13 -13
- cirq/transformers/analytical_decompositions/clifford_decomposition.py +8 -7
- cirq/transformers/analytical_decompositions/clifford_decomposition_test.py +5 -5
- cirq/transformers/analytical_decompositions/controlled_gate_decomposition.py +11 -10
- cirq/transformers/analytical_decompositions/controlled_gate_decomposition_test.py +6 -6
- cirq/transformers/analytical_decompositions/cphase_to_fsim.py +3 -2
- cirq/transformers/analytical_decompositions/cphase_to_fsim_test.py +11 -10
- cirq/transformers/analytical_decompositions/quantum_shannon_decomposition.py +8 -7
- cirq/transformers/analytical_decompositions/quantum_shannon_decomposition_test.py +17 -20
- cirq/transformers/analytical_decompositions/single_qubit_decompositions_test.py +33 -27
- cirq/transformers/analytical_decompositions/single_to_two_qubit_isometry_test.py +1 -1
- cirq/transformers/analytical_decompositions/three_qubit_decomposition.py +1 -1
- cirq/transformers/analytical_decompositions/two_qubit_state_preparation_test.py +12 -11
- cirq/transformers/analytical_decompositions/two_qubit_to_cz.py +5 -2
- cirq/transformers/analytical_decompositions/two_qubit_to_cz_test.py +3 -3
- cirq/transformers/analytical_decompositions/two_qubit_to_fsim.py +2 -1
- cirq/transformers/analytical_decompositions/two_qubit_to_ms.py +2 -1
- cirq/transformers/analytical_decompositions/two_qubit_to_ms_test.py +2 -2
- cirq/transformers/analytical_decompositions/two_qubit_to_sqrt_iswap.py +2 -1
- cirq/transformers/analytical_decompositions/two_qubit_to_sqrt_iswap_test.py +32 -30
- cirq/transformers/drop_negligible_operations_test.py +7 -7
- cirq/transformers/dynamical_decoupling.py +185 -112
- cirq/transformers/dynamical_decoupling_test.py +195 -201
- cirq/transformers/eject_phased_paulis.py +2 -1
- cirq/transformers/eject_phased_paulis_test.py +3 -2
- cirq/transformers/eject_z.py +5 -3
- cirq/transformers/eject_z_test.py +23 -25
- cirq/transformers/expand_composite.py +3 -2
- cirq/transformers/expand_composite_test.py +14 -14
- cirq/transformers/gauge_compiling/__init__.py +13 -0
- cirq/transformers/gauge_compiling/gauge_compiling.py +3 -2
- cirq/transformers/gauge_compiling/gauge_compiling_test.py +14 -12
- cirq/transformers/gauge_compiling/gauge_compiling_test_utils.py +3 -3
- cirq/transformers/gauge_compiling/idle_moments_gauge.py +225 -0
- cirq/transformers/gauge_compiling/idle_moments_gauge_test.py +193 -0
- cirq/transformers/gauge_compiling/multi_moment_cphase_gauge.py +242 -0
- cirq/transformers/gauge_compiling/multi_moment_cphase_gauge_test.py +243 -0
- cirq/transformers/gauge_compiling/multi_moment_gauge_compiling.py +151 -0
- cirq/transformers/gauge_compiling/sqrt_cz_gauge.py +2 -1
- cirq/transformers/heuristic_decompositions/gate_tabulation_math_utils.py +1 -1
- cirq/transformers/heuristic_decompositions/gate_tabulation_math_utils_test.py +6 -6
- cirq/transformers/heuristic_decompositions/two_qubit_gate_tabulation.py +3 -2
- cirq/transformers/measurement_transformers.py +2 -1
- cirq/transformers/measurement_transformers_test.py +45 -39
- cirq/transformers/merge_k_qubit_gates.py +2 -1
- cirq/transformers/merge_k_qubit_gates_test.py +1 -1
- cirq/transformers/merge_single_qubit_gates.py +9 -5
- cirq/transformers/merge_single_qubit_gates_test.py +22 -22
- cirq/transformers/noise_adding_test.py +2 -2
- cirq/transformers/optimize_for_target_gateset.py +2 -1
- cirq/transformers/optimize_for_target_gateset_test.py +11 -9
- cirq/transformers/qubit_management_transformers_test.py +6 -2
- cirq/transformers/routing/mapping_manager.py +2 -1
- cirq/transformers/routing/route_circuit_cqc.py +2 -1
- cirq/transformers/stratify.py +2 -1
- cirq/transformers/symbolize.py +2 -1
- cirq/transformers/tag_transformers.py +2 -1
- cirq/transformers/target_gatesets/compilation_target_gateset.py +2 -1
- cirq/transformers/target_gatesets/cz_gateset.py +2 -1
- cirq/transformers/target_gatesets/cz_gateset_test.py +1 -1
- cirq/transformers/target_gatesets/sqrt_iswap_gateset.py +2 -1
- cirq/transformers/transformer_api.py +2 -1
- cirq/transformers/transformer_primitives.py +271 -145
- cirq/transformers/transformer_primitives_test.py +185 -1
- cirq/value/abc_alt.py +2 -1
- cirq/value/classical_data.py +2 -1
- cirq/value/condition.py +2 -1
- cirq/value/digits.py +9 -2
- cirq/value/duration.py +6 -5
- cirq/value/linear_dict.py +4 -9
- cirq/value/measurement_key.py +2 -1
- cirq/value/periodic_value.py +3 -2
- cirq/value/product_state.py +2 -1
- cirq/value/value_equality_attr.py +2 -1
- cirq/vis/density_matrix.py +1 -1
- cirq/vis/heatmap.py +2 -1
- cirq/vis/histogram.py +2 -1
- cirq/vis/state_histogram.py +2 -1
- cirq/work/collector.py +2 -1
- cirq/work/observable_grouping.py +2 -1
- cirq/work/observable_measurement.py +2 -1
- cirq/work/observable_measurement_data.py +2 -1
- cirq/work/observable_measurement_test.py +1 -1
- cirq/work/observable_readout_calibration.py +2 -1
- cirq/work/observable_readout_calibration_test.py +1 -1
- cirq/work/observable_settings.py +2 -1
- cirq/work/sampler.py +2 -1
- cirq/work/sampler_test.py +1 -1
- {cirq_core-1.7.0.dev20250825174419.dist-info → cirq_core-1.7.0.dev20251203004401.dist-info}/METADATA +5 -6
- {cirq_core-1.7.0.dev20250825174419.dist-info → cirq_core-1.7.0.dev20251203004401.dist-info}/RECORD +425 -406
- cirq/contrib/json_test.py +0 -33
- {cirq_core-1.7.0.dev20250825174419.dist-info → cirq_core-1.7.0.dev20251203004401.dist-info}/WHEEL +0 -0
- {cirq_core-1.7.0.dev20250825174419.dist-info → cirq_core-1.7.0.dev20251203004401.dist-info}/licenses/LICENSE +0 -0
- {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
|