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