qiskit 1.1.2__cp38-abi3-win32.whl → 1.2.0__cp38-abi3-win32.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.
- qiskit/VERSION.txt +1 -1
- qiskit/__init__.py +27 -24
- qiskit/_accelerate.pyd +0 -0
- qiskit/_numpy_compat.py +1 -1
- qiskit/assembler/assemble_circuits.py +107 -64
- qiskit/assembler/assemble_schedules.py +5 -12
- qiskit/assembler/disassemble.py +10 -1
- qiskit/circuit/__init__.py +6 -3
- qiskit/circuit/_classical_resource_map.py +5 -5
- qiskit/circuit/_utils.py +0 -13
- qiskit/circuit/add_control.py +1 -1
- qiskit/circuit/annotated_operation.py +23 -1
- qiskit/circuit/classical/expr/expr.py +4 -4
- qiskit/circuit/classical/expr/visitors.py +1 -1
- qiskit/circuit/classical/types/__init__.py +1 -1
- qiskit/circuit/classical/types/types.py +2 -2
- qiskit/circuit/classicalfunction/boolean_expression.py +1 -1
- qiskit/circuit/classicalfunction/classical_function_visitor.py +5 -5
- qiskit/circuit/classicalfunction/utils.py +1 -1
- qiskit/circuit/classicalregister.py +1 -1
- qiskit/circuit/commutation_checker.py +83 -35
- qiskit/circuit/controlflow/_builder_utils.py +1 -1
- qiskit/circuit/controlflow/builder.py +10 -6
- qiskit/circuit/controlflow/if_else.py +2 -2
- qiskit/circuit/controlflow/switch_case.py +1 -1
- qiskit/circuit/delay.py +1 -1
- qiskit/circuit/duration.py +2 -2
- qiskit/circuit/equivalence.py +5 -7
- qiskit/circuit/gate.py +11 -8
- qiskit/circuit/instruction.py +31 -13
- qiskit/circuit/instructionset.py +2 -5
- qiskit/circuit/library/__init__.py +2 -1
- qiskit/circuit/library/arithmetic/linear_amplitude_function.py +1 -1
- qiskit/circuit/library/arithmetic/linear_pauli_rotations.py +1 -1
- qiskit/circuit/library/arithmetic/piecewise_chebyshev.py +1 -1
- qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py +1 -1
- qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py +3 -3
- qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py +1 -1
- qiskit/circuit/library/basis_change/__init__.py +1 -1
- qiskit/circuit/library/basis_change/qft.py +40 -6
- qiskit/circuit/library/blueprintcircuit.py +3 -5
- qiskit/circuit/library/data_preparation/__init__.py +9 -2
- qiskit/circuit/library/data_preparation/initializer.py +8 -0
- qiskit/circuit/library/data_preparation/state_preparation.py +98 -178
- qiskit/circuit/library/generalized_gates/isometry.py +8 -8
- qiskit/circuit/library/generalized_gates/linear_function.py +3 -2
- qiskit/circuit/library/generalized_gates/mcg_up_to_diagonal.py +4 -4
- qiskit/circuit/library/generalized_gates/permutation.py +8 -9
- qiskit/circuit/library/generalized_gates/uc.py +3 -3
- qiskit/circuit/library/generalized_gates/uc_pauli_rot.py +2 -2
- qiskit/circuit/library/generalized_gates/unitary.py +13 -11
- qiskit/circuit/library/graph_state.py +1 -1
- qiskit/circuit/library/hamiltonian_gate.py +1 -2
- qiskit/circuit/library/hidden_linear_function.py +1 -1
- qiskit/circuit/library/n_local/evolved_operator_ansatz.py +3 -2
- qiskit/circuit/library/n_local/n_local.py +4 -5
- qiskit/circuit/library/n_local/pauli_two_design.py +1 -1
- qiskit/circuit/library/n_local/qaoa_ansatz.py +6 -8
- qiskit/circuit/library/n_local/two_local.py +1 -1
- qiskit/circuit/library/overlap.py +11 -5
- qiskit/circuit/library/pauli_evolution.py +7 -3
- qiskit/circuit/library/standard_gates/dcx.py +3 -0
- qiskit/circuit/library/standard_gates/ecr.py +3 -0
- qiskit/circuit/library/standard_gates/global_phase.py +3 -0
- qiskit/circuit/library/standard_gates/h.py +13 -5
- qiskit/circuit/library/standard_gates/i.py +3 -0
- qiskit/circuit/library/standard_gates/iswap.py +3 -0
- qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +19 -10
- qiskit/circuit/library/standard_gates/p.py +14 -9
- qiskit/circuit/library/standard_gates/r.py +3 -0
- qiskit/circuit/library/standard_gates/rx.py +21 -6
- qiskit/circuit/library/standard_gates/rxx.py +40 -1
- qiskit/circuit/library/standard_gates/ry.py +21 -6
- qiskit/circuit/library/standard_gates/ryy.py +40 -1
- qiskit/circuit/library/standard_gates/rz.py +22 -6
- qiskit/circuit/library/standard_gates/rzx.py +40 -1
- qiskit/circuit/library/standard_gates/rzz.py +41 -2
- qiskit/circuit/library/standard_gates/s.py +77 -0
- qiskit/circuit/library/standard_gates/swap.py +12 -5
- qiskit/circuit/library/standard_gates/sx.py +14 -5
- qiskit/circuit/library/standard_gates/t.py +5 -0
- qiskit/circuit/library/standard_gates/u.py +22 -7
- qiskit/circuit/library/standard_gates/u1.py +8 -3
- qiskit/circuit/library/standard_gates/u2.py +3 -0
- qiskit/circuit/library/standard_gates/u3.py +22 -7
- qiskit/circuit/library/standard_gates/x.py +156 -92
- qiskit/circuit/library/standard_gates/xx_minus_yy.py +40 -1
- qiskit/circuit/library/standard_gates/xx_plus_yy.py +52 -11
- qiskit/circuit/library/standard_gates/y.py +6 -1
- qiskit/circuit/library/standard_gates/z.py +8 -1
- qiskit/circuit/operation.py +1 -1
- qiskit/circuit/parameter.py +9 -10
- qiskit/circuit/parameterexpression.py +16 -13
- qiskit/circuit/parametertable.py +1 -190
- qiskit/circuit/parametervector.py +1 -1
- qiskit/circuit/quantumcircuit.py +395 -387
- qiskit/circuit/quantumcircuitdata.py +3 -5
- qiskit/circuit/quantumregister.py +1 -1
- qiskit/circuit/random/__init__.py +1 -1
- qiskit/circuit/random/utils.py +175 -26
- qiskit/circuit/register.py +5 -7
- qiskit/circuit/singleton.py +3 -3
- qiskit/circuit/tools/pi_check.py +4 -4
- qiskit/compiler/assembler.py +95 -24
- qiskit/compiler/scheduler.py +2 -2
- qiskit/compiler/transpiler.py +42 -128
- qiskit/converters/circuit_to_dag.py +4 -6
- qiskit/converters/circuit_to_gate.py +4 -8
- qiskit/converters/circuit_to_instruction.py +5 -17
- qiskit/converters/dag_to_circuit.py +2 -6
- qiskit/dagcircuit/collect_blocks.py +2 -2
- qiskit/dagcircuit/dagcircuit.py +190 -187
- qiskit/dagcircuit/dagdependency.py +4 -4
- qiskit/dagcircuit/dagdependency_v2.py +4 -4
- qiskit/dagcircuit/dagdepnode.py +1 -1
- qiskit/dagcircuit/dagnode.py +66 -157
- qiskit/passmanager/flow_controllers.py +1 -1
- qiskit/passmanager/passmanager.py +3 -3
- qiskit/primitives/__init__.py +1 -5
- qiskit/primitives/backend_estimator.py +25 -15
- qiskit/primitives/backend_estimator_v2.py +31 -7
- qiskit/primitives/backend_sampler.py +21 -12
- qiskit/primitives/backend_sampler_v2.py +12 -3
- qiskit/primitives/base/base_estimator.py +31 -4
- qiskit/primitives/base/base_primitive.py +2 -2
- qiskit/primitives/base/base_result.py +2 -2
- qiskit/primitives/base/base_sampler.py +26 -2
- qiskit/primitives/base/estimator_result.py +2 -2
- qiskit/primitives/base/sampler_result.py +2 -2
- qiskit/primitives/containers/__init__.py +0 -1
- qiskit/primitives/containers/bindings_array.py +2 -2
- qiskit/primitives/containers/bit_array.py +108 -10
- qiskit/primitives/containers/shape.py +3 -3
- qiskit/primitives/estimator.py +9 -2
- qiskit/primitives/primitive_job.py +1 -1
- qiskit/primitives/sampler.py +10 -3
- qiskit/primitives/statevector_estimator.py +5 -3
- qiskit/primitives/statevector_sampler.py +11 -5
- qiskit/primitives/utils.py +16 -0
- qiskit/providers/backend.py +15 -6
- qiskit/providers/backend_compat.py +7 -4
- qiskit/providers/basic_provider/basic_provider_tools.py +1 -1
- qiskit/providers/basic_provider/basic_simulator.py +33 -25
- qiskit/providers/fake_provider/fake_backend.py +10 -3
- qiskit/providers/fake_provider/fake_openpulse_2q.py +157 -149
- qiskit/providers/fake_provider/fake_openpulse_3q.py +228 -220
- qiskit/providers/fake_provider/fake_pulse_backend.py +2 -1
- qiskit/providers/fake_provider/fake_qasm_backend.py +7 -2
- qiskit/providers/fake_provider/generic_backend_v2.py +514 -68
- qiskit/providers/models/__init__.py +48 -11
- qiskit/providers/models/backendconfiguration.py +50 -4
- qiskit/providers/models/backendproperties.py +13 -2
- qiskit/providers/models/pulsedefaults.py +10 -11
- qiskit/providers/options.py +13 -13
- qiskit/providers/providerutils.py +3 -1
- qiskit/pulse/configuration.py +8 -12
- qiskit/pulse/instruction_schedule_map.py +3 -5
- qiskit/pulse/instructions/acquire.py +7 -8
- qiskit/pulse/instructions/instruction.py +2 -3
- qiskit/pulse/library/samplers/decorators.py +5 -9
- qiskit/pulse/library/symbolic_pulses.py +4 -7
- qiskit/pulse/library/waveform.py +2 -5
- qiskit/pulse/macros.py +11 -6
- qiskit/pulse/parser.py +8 -10
- qiskit/pulse/schedule.py +9 -17
- qiskit/pulse/transforms/alignments.py +1 -3
- qiskit/pulse/utils.py +1 -2
- qiskit/qasm/libs/stdgates.inc +35 -28
- qiskit/qasm2/__init__.py +7 -7
- qiskit/qasm2/export.py +5 -9
- qiskit/qasm2/parse.py +1 -1
- qiskit/qasm3/ast.py +9 -25
- qiskit/qasm3/exporter.py +582 -479
- qiskit/qasm3/printer.py +7 -16
- qiskit/qobj/common.py +10 -0
- qiskit/qobj/converters/lo_config.py +9 -0
- qiskit/qobj/converters/pulse_instruction.py +13 -6
- qiskit/qobj/pulse_qobj.py +69 -15
- qiskit/qobj/qasm_qobj.py +72 -20
- qiskit/qobj/utils.py +9 -0
- qiskit/qpy/__init__.py +1 -1
- qiskit/qpy/binary_io/circuits.py +8 -5
- qiskit/qpy/binary_io/schedules.py +1 -1
- qiskit/qpy/binary_io/value.py +3 -3
- qiskit/qpy/interface.py +3 -2
- qiskit/qpy/type_keys.py +2 -2
- qiskit/quantum_info/operators/channel/quantum_channel.py +3 -6
- qiskit/quantum_info/operators/channel/superop.py +2 -2
- qiskit/quantum_info/operators/channel/transformations.py +1 -1
- qiskit/quantum_info/operators/dihedral/dihedral.py +3 -4
- qiskit/quantum_info/operators/dihedral/dihedral_circuits.py +1 -3
- qiskit/quantum_info/operators/dihedral/random.py +6 -3
- qiskit/quantum_info/operators/measures.py +2 -2
- qiskit/quantum_info/operators/op_shape.py +12 -20
- qiskit/quantum_info/operators/operator.py +14 -21
- qiskit/quantum_info/operators/predicates.py +1 -0
- qiskit/quantum_info/operators/symplectic/base_pauli.py +7 -11
- qiskit/quantum_info/operators/symplectic/clifford.py +1 -1
- qiskit/quantum_info/operators/symplectic/pauli.py +3 -3
- qiskit/quantum_info/operators/symplectic/pauli_list.py +9 -10
- qiskit/quantum_info/operators/symplectic/random.py +1 -1
- qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py +15 -17
- qiskit/quantum_info/quaternion.py +1 -1
- qiskit/quantum_info/states/densitymatrix.py +5 -8
- qiskit/quantum_info/states/stabilizerstate.py +128 -37
- qiskit/quantum_info/states/statevector.py +4 -8
- qiskit/result/counts.py +2 -2
- qiskit/result/mitigation/correlated_readout_mitigator.py +2 -2
- qiskit/result/mitigation/local_readout_mitigator.py +2 -2
- qiskit/result/mitigation/utils.py +1 -3
- qiskit/result/models.py +17 -16
- qiskit/result/result.py +15 -20
- qiskit/scheduler/lowering.py +2 -2
- qiskit/synthesis/__init__.py +2 -1
- qiskit/synthesis/clifford/__init__.py +1 -1
- qiskit/synthesis/clifford/clifford_decompose_ag.py +2 -2
- qiskit/synthesis/clifford/clifford_decompose_bm.py +10 -240
- qiskit/synthesis/clifford/clifford_decompose_greedy.py +9 -303
- qiskit/synthesis/clifford/clifford_decompose_layers.py +25 -23
- qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_full.py +1 -1
- qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_general.py +1 -1
- qiskit/synthesis/discrete_basis/generate_basis_approximations.py +1 -1
- qiskit/synthesis/discrete_basis/solovay_kitaev.py +2 -2
- qiskit/synthesis/evolution/evolution_synthesis.py +4 -2
- qiskit/synthesis/evolution/lie_trotter.py +46 -19
- qiskit/synthesis/evolution/product_formula.py +111 -55
- qiskit/synthesis/evolution/qdrift.py +40 -10
- qiskit/synthesis/evolution/suzuki_trotter.py +43 -33
- qiskit/synthesis/linear/__init__.py +1 -0
- qiskit/synthesis/linear/cnot_synth.py +22 -96
- qiskit/synthesis/linear/linear_depth_lnn.py +8 -8
- qiskit/synthesis/linear/linear_matrix_utils.py +13 -161
- qiskit/synthesis/linear_phase/cnot_phase_synth.py +1 -1
- qiskit/synthesis/linear_phase/cx_cz_depth_lnn.py +3 -3
- qiskit/synthesis/linear_phase/cz_depth_lnn.py +1 -1
- qiskit/synthesis/one_qubit/one_qubit_decompose.py +29 -29
- qiskit/synthesis/permutation/permutation_full.py +5 -29
- qiskit/synthesis/permutation/permutation_lnn.py +2 -24
- qiskit/synthesis/permutation/permutation_utils.py +2 -59
- qiskit/synthesis/qft/__init__.py +1 -0
- qiskit/synthesis/qft/qft_decompose_full.py +79 -0
- qiskit/synthesis/qft/qft_decompose_lnn.py +17 -9
- qiskit/synthesis/stabilizer/stabilizer_circuit.py +6 -6
- qiskit/synthesis/stabilizer/stabilizer_decompose.py +2 -2
- qiskit/synthesis/two_qubit/local_invariance.py +8 -38
- qiskit/synthesis/two_qubit/two_qubit_decompose.py +48 -129
- qiskit/synthesis/unitary/aqc/cnot_structures.py +1 -1
- qiskit/synthesis/unitary/qsd.py +5 -3
- qiskit/transpiler/__init__.py +1 -0
- qiskit/transpiler/basepasses.py +1 -1
- qiskit/transpiler/coupling.py +3 -3
- qiskit/transpiler/instruction_durations.py +1 -2
- qiskit/transpiler/layout.py +3 -3
- qiskit/transpiler/passes/__init__.py +2 -0
- qiskit/transpiler/passes/basis/basis_translator.py +84 -64
- qiskit/transpiler/passes/basis/translate_parameterized.py +3 -5
- qiskit/transpiler/passes/basis/unroll_3q_or_more.py +1 -1
- qiskit/transpiler/passes/basis/unroll_custom_definitions.py +10 -10
- qiskit/transpiler/passes/calibration/rx_builder.py +3 -3
- qiskit/transpiler/passes/calibration/rzx_builder.py +3 -3
- qiskit/transpiler/passes/layout/apply_layout.py +13 -3
- qiskit/transpiler/passes/layout/sabre_layout.py +10 -8
- qiskit/transpiler/passes/layout/sabre_pre_layout.py +4 -1
- qiskit/transpiler/passes/layout/set_layout.py +2 -2
- qiskit/transpiler/passes/layout/vf2_layout.py +1 -1
- qiskit/transpiler/passes/layout/vf2_utils.py +3 -3
- qiskit/transpiler/passes/optimization/__init__.py +1 -0
- qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py +2 -2
- qiskit/transpiler/passes/optimization/commutation_analysis.py +7 -10
- qiskit/transpiler/passes/optimization/commutative_cancellation.py +35 -19
- qiskit/transpiler/passes/optimization/consolidate_blocks.py +17 -8
- qiskit/transpiler/passes/optimization/inverse_cancellation.py +6 -6
- qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +64 -41
- qiskit/transpiler/passes/optimization/optimize_1q_gates.py +1 -1
- qiskit/transpiler/passes/optimization/split_2q_unitaries.py +83 -0
- qiskit/transpiler/passes/optimization/template_matching/backward_match.py +1 -1
- qiskit/transpiler/passes/optimization/template_matching/forward_match.py +2 -2
- qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +1 -1
- qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py +3 -2
- qiskit/transpiler/passes/routing/commuting_2q_gate_routing/swap_strategy.py +1 -1
- qiskit/transpiler/passes/routing/layout_transformation.py +2 -1
- qiskit/transpiler/passes/routing/sabre_swap.py +35 -26
- qiskit/transpiler/passes/routing/star_prerouting.py +80 -105
- qiskit/transpiler/passes/routing/stochastic_swap.py +1 -3
- qiskit/transpiler/passes/scheduling/alap.py +1 -2
- qiskit/transpiler/passes/scheduling/alignments/__init__.py +2 -2
- qiskit/transpiler/passes/scheduling/alignments/check_durations.py +1 -1
- qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py +2 -2
- qiskit/transpiler/passes/scheduling/alignments/reschedule.py +1 -1
- qiskit/transpiler/passes/scheduling/asap.py +1 -2
- qiskit/transpiler/passes/scheduling/base_scheduler.py +5 -5
- qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +3 -3
- qiskit/transpiler/passes/scheduling/padding/base_padding.py +1 -1
- qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +20 -14
- qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py +7 -6
- qiskit/transpiler/passes/scheduling/time_unit_conversion.py +4 -3
- qiskit/transpiler/passes/synthesis/high_level_synthesis.py +211 -36
- qiskit/transpiler/passes/synthesis/plugin.py +2 -2
- qiskit/transpiler/passes/synthesis/unitary_synthesis.py +80 -40
- qiskit/transpiler/passes/utils/__init__.py +0 -1
- qiskit/transpiler/passes/utils/check_gate_direction.py +4 -4
- qiskit/transpiler/passes/utils/check_map.py +3 -6
- qiskit/transpiler/passes/utils/convert_conditions_to_if_ops.py +3 -4
- qiskit/transpiler/passes/utils/error.py +2 -2
- qiskit/transpiler/passes/utils/fixed_point.py +3 -3
- qiskit/transpiler/passes/utils/gate_direction.py +1 -1
- qiskit/transpiler/passes/utils/gates_basis.py +1 -2
- qiskit/transpiler/passmanager.py +7 -6
- qiskit/transpiler/preset_passmanagers/__init__.py +4 -228
- qiskit/transpiler/preset_passmanagers/builtin_plugins.py +73 -18
- qiskit/transpiler/preset_passmanagers/common.py +3 -6
- qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +518 -0
- qiskit/transpiler/preset_passmanagers/level0.py +1 -1
- qiskit/transpiler/target.py +27 -8
- qiskit/user_config.py +29 -6
- qiskit/utils/classtools.py +3 -3
- qiskit/utils/deprecation.py +3 -2
- qiskit/utils/lazy_tester.py +2 -2
- qiskit/utils/optionals.py +8 -8
- qiskit/visualization/bloch.py +18 -23
- qiskit/visualization/circuit/_utils.py +34 -10
- qiskit/visualization/circuit/circuit_visualization.py +23 -16
- qiskit/visualization/circuit/latex.py +29 -27
- qiskit/visualization/circuit/matplotlib.py +4 -2
- qiskit/visualization/circuit/qcstyle.py +2 -2
- qiskit/visualization/circuit/text.py +9 -15
- qiskit/visualization/dag_visualization.py +2 -2
- qiskit/visualization/pulse_v2/core.py +1 -1
- qiskit/visualization/pulse_v2/events.py +1 -1
- qiskit/visualization/pulse_v2/generators/frame.py +3 -4
- qiskit/visualization/pulse_v2/generators/waveform.py +5 -9
- qiskit/visualization/pulse_v2/layouts.py +1 -5
- qiskit/visualization/pulse_v2/plotters/matplotlib.py +1 -2
- qiskit/visualization/state_visualization.py +5 -6
- qiskit/visualization/timeline/plotters/matplotlib.py +1 -2
- qiskit/visualization/transition_visualization.py +7 -2
- {qiskit-1.1.2.dist-info → qiskit-1.2.0.dist-info}/METADATA +12 -12
- {qiskit-1.1.2.dist-info → qiskit-1.2.0.dist-info}/RECORD +342 -340
- {qiskit-1.1.2.dist-info → qiskit-1.2.0.dist-info}/entry_points.txt +3 -0
- qiskit/transpiler/passes/utils/block_to_matrix.py +0 -47
- {qiskit-1.1.2.dist-info → qiskit-1.2.0.dist-info}/LICENSE.txt +0 -0
- {qiskit-1.1.2.dist-info → qiskit-1.2.0.dist-info}/WHEEL +0 -0
- {qiskit-1.1.2.dist-info → qiskit-1.2.0.dist-info}/top_level.txt +0 -0
qiskit/qasm3/exporter.py
CHANGED
@@ -12,20 +12,26 @@
|
|
12
12
|
|
13
13
|
"""QASM3 Exporter"""
|
14
14
|
|
15
|
+
from __future__ import annotations
|
16
|
+
|
15
17
|
import collections
|
16
|
-
import
|
18
|
+
import contextlib
|
19
|
+
import dataclasses
|
17
20
|
import io
|
18
21
|
import itertools
|
22
|
+
import math
|
19
23
|
import numbers
|
20
|
-
|
24
|
+
import re
|
21
25
|
from typing import Iterable, List, Sequence, Union
|
22
26
|
|
27
|
+
from qiskit._accelerate.circuit import StandardGate
|
23
28
|
from qiskit.circuit import (
|
29
|
+
library,
|
24
30
|
Barrier,
|
25
31
|
CircuitInstruction,
|
26
32
|
Clbit,
|
33
|
+
ControlledGate,
|
27
34
|
Gate,
|
28
|
-
Instruction,
|
29
35
|
Measure,
|
30
36
|
Parameter,
|
31
37
|
ParameterExpression,
|
@@ -47,7 +53,6 @@ from qiskit.circuit.controlflow import (
|
|
47
53
|
ContinueLoopOp,
|
48
54
|
CASE_DEFAULT,
|
49
55
|
)
|
50
|
-
from qiskit.circuit.library import standard_gates
|
51
56
|
from qiskit.circuit.register import Register
|
52
57
|
from qiskit.circuit.tools import pi_check
|
53
58
|
|
@@ -115,13 +120,9 @@ _RESERVED_KEYWORDS = frozenset(
|
|
115
120
|
# This probably isn't precisely the same as the OQ3 spec, but we'd need an extra dependency to fully
|
116
121
|
# handle all Unicode character classes, and this should be close enough for users who aren't
|
117
122
|
# actively _trying_ to break us (fingers crossed).
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
def _escape_invalid_identifier(name: str) -> str:
|
122
|
-
if name in _RESERVED_KEYWORDS or not _VALID_IDENTIFIER.fullmatch(name):
|
123
|
-
name = "_" + re.sub(r"[^\w\d]", "_", name)
|
124
|
-
return name
|
123
|
+
_VALID_DECLARABLE_IDENTIFIER = re.compile(r"([\w][\w\d]*)", flags=re.U)
|
124
|
+
_VALID_HARDWARE_QUBIT = re.compile(r"\$[\d]+", flags=re.U)
|
125
|
+
_BAD_IDENTIFIER_CHARACTERS = re.compile(r"[^\w\d]", flags=re.U)
|
125
126
|
|
126
127
|
|
127
128
|
class Exporter:
|
@@ -196,134 +197,325 @@ class Exporter:
|
|
196
197
|
)
|
197
198
|
|
198
199
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
"
|
208
|
-
"
|
209
|
-
"
|
210
|
-
"
|
211
|
-
"
|
212
|
-
"
|
213
|
-
"
|
214
|
-
"
|
215
|
-
"
|
216
|
-
"
|
217
|
-
"
|
218
|
-
"
|
219
|
-
"
|
220
|
-
"
|
221
|
-
"
|
222
|
-
"
|
223
|
-
"
|
224
|
-
"
|
225
|
-
"
|
226
|
-
"
|
227
|
-
"
|
228
|
-
"
|
229
|
-
"
|
230
|
-
"
|
231
|
-
"
|
232
|
-
"
|
233
|
-
"
|
234
|
-
"
|
235
|
-
"
|
236
|
-
"
|
237
|
-
|
238
|
-
|
200
|
+
# Just needs to have enough parameters to support the largest standard (non-controlled) gate in our
|
201
|
+
# standard library. We have to use the same `Parameter` instances each time so the equality
|
202
|
+
# comparisons will work.
|
203
|
+
_FIXED_PARAMETERS = (Parameter("p0"), Parameter("p1"), Parameter("p2"), Parameter("p3"))
|
204
|
+
|
205
|
+
# Mapping of symbols defined by `stdgates.inc` to their gate definition source.
|
206
|
+
_KNOWN_INCLUDES = {
|
207
|
+
"stdgates.inc": {
|
208
|
+
"p": library.PhaseGate(*_FIXED_PARAMETERS[:1]),
|
209
|
+
"x": library.XGate(),
|
210
|
+
"y": library.YGate(),
|
211
|
+
"z": library.ZGate(),
|
212
|
+
"h": library.HGate(),
|
213
|
+
"s": library.SGate(),
|
214
|
+
"sdg": library.SdgGate(),
|
215
|
+
"t": library.TGate(),
|
216
|
+
"tdg": library.TdgGate(),
|
217
|
+
"sx": library.SXGate(),
|
218
|
+
"rx": library.RXGate(*_FIXED_PARAMETERS[:1]),
|
219
|
+
"ry": library.RYGate(*_FIXED_PARAMETERS[:1]),
|
220
|
+
"rz": library.RZGate(*_FIXED_PARAMETERS[:1]),
|
221
|
+
"cx": library.CXGate(),
|
222
|
+
"cy": library.CYGate(),
|
223
|
+
"cz": library.CZGate(),
|
224
|
+
"cp": library.CPhaseGate(*_FIXED_PARAMETERS[:1]),
|
225
|
+
"crx": library.CRXGate(*_FIXED_PARAMETERS[:1]),
|
226
|
+
"cry": library.CRYGate(*_FIXED_PARAMETERS[:1]),
|
227
|
+
"crz": library.CRZGate(*_FIXED_PARAMETERS[:1]),
|
228
|
+
"ch": library.CHGate(),
|
229
|
+
"swap": library.SwapGate(),
|
230
|
+
"ccx": library.CCXGate(),
|
231
|
+
"cswap": library.CSwapGate(),
|
232
|
+
"cu": library.CUGate(*_FIXED_PARAMETERS[:4]),
|
233
|
+
"CX": library.CXGate(),
|
234
|
+
"phase": library.PhaseGate(*_FIXED_PARAMETERS[:1]),
|
235
|
+
"cphase": library.CPhaseGate(*_FIXED_PARAMETERS[:1]),
|
236
|
+
"id": library.IGate(),
|
237
|
+
"u1": library.U1Gate(*_FIXED_PARAMETERS[:1]),
|
238
|
+
"u2": library.U2Gate(*_FIXED_PARAMETERS[:2]),
|
239
|
+
"u3": library.U3Gate(*_FIXED_PARAMETERS[:3]),
|
240
|
+
},
|
241
|
+
}
|
242
|
+
|
243
|
+
_BUILTIN_GATES = {
|
244
|
+
"U": library.UGate(*_FIXED_PARAMETERS[:3]),
|
245
|
+
}
|
246
|
+
|
247
|
+
|
248
|
+
@dataclasses.dataclass
|
249
|
+
class GateInfo:
|
250
|
+
"""Symbol-table information on a gate."""
|
251
|
+
|
252
|
+
canonical: Gate | None
|
253
|
+
"""The canonical object for the gate. This is a Qiskit object that is not necessarily equal to
|
254
|
+
any usage, but is the canonical form in terms of its parameter usage, such as a standard-library
|
255
|
+
gate being defined in terms of the `_FIXED_PARAMETERS` objects. A call-site gate whose
|
256
|
+
canonical form equals this can use the corresponding symbol as the callee.
|
257
|
+
|
258
|
+
This can be ``None`` if the gate was an overridden "basis gate" for this export, so no canonical
|
259
|
+
form is known."""
|
260
|
+
node: ast.QuantumGateDefinition | None
|
261
|
+
"""An AST node containing the gate definition. This can be ``None`` if the gate came from an
|
262
|
+
included file, or is an overridden "basis gate" of the export."""
|
263
|
+
|
264
|
+
|
265
|
+
class SymbolTable:
|
266
|
+
"""Track Qiskit objects and the OQ3 identifiers used to refer to them."""
|
267
|
+
|
268
|
+
def __init__(self):
|
269
|
+
self.gates: collections.OrderedDict[str, GateInfo | None] = {}
|
270
|
+
"""Mapping of the symbol name to the "definition source" of the gate, which provides its
|
271
|
+
signature and decomposition. The definition source can be `None` if the user set the gate
|
272
|
+
as a custom "basis gate".
|
273
|
+
|
274
|
+
Gates can only be declared in the global scope, so there is just a single look-up for this.
|
275
|
+
|
276
|
+
This is insertion ordered, and that can be relied on for iteration later."""
|
277
|
+
self.standard_gate_idents: dict[StandardGate, ast.Identifier] = {}
|
278
|
+
"""Mapping of standard gate enumeration values to the identifier we represent that as."""
|
279
|
+
self.user_gate_idents: dict[int, ast.Identifier] = {}
|
280
|
+
"""Mapping of `id`s of user gates to the identifier we use for it."""
|
281
|
+
|
282
|
+
self.variables: list[dict[str, object]] = [{}]
|
283
|
+
"""Stack of mappings of variable names to the Qiskit object that represents them.
|
284
|
+
|
285
|
+
The zeroth index corresponds to the global scope, the highest index to the current scope."""
|
286
|
+
self.objects: list[dict[object, ast.Identifier]] = [{}]
|
287
|
+
"""Stack of mappings of Qiskit objects to the identifier (or subscripted identifier) that
|
288
|
+
refers to them. This is similar to the inverse mapping of ``variables``.
|
289
|
+
|
290
|
+
The zeroth index corresponds to the global scope, the highest index to the current scope."""
|
291
|
+
|
292
|
+
# Quick-and-dirty method of getting unique salts for names.
|
293
|
+
self._counter = itertools.count()
|
239
294
|
|
240
|
-
def
|
241
|
-
|
242
|
-
self.
|
295
|
+
def push_scope(self):
|
296
|
+
"""Enter a new variable scope."""
|
297
|
+
self.variables.append({})
|
298
|
+
self.objects.append({})
|
299
|
+
|
300
|
+
def pop_scope(self):
|
301
|
+
"""Exit the current scope, returning to a previous scope."""
|
302
|
+
self.objects.pop()
|
303
|
+
self.variables.pop()
|
304
|
+
|
305
|
+
def new_context(self) -> SymbolTable:
|
306
|
+
"""Create a new context, such as for a gate definition.
|
307
|
+
|
308
|
+
Contexts share the same set of globally defined gates, but have no access to other variables
|
309
|
+
defined in any scope."""
|
310
|
+
out = SymbolTable()
|
311
|
+
out.gates = self.gates
|
312
|
+
out.standard_gate_idents = self.standard_gate_idents
|
313
|
+
out.user_gate_idents = self.user_gate_idents
|
314
|
+
return out
|
243
315
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
316
|
+
def symbol_defined(self, name: str) -> bool:
|
317
|
+
"""Whether this identifier has a defined meaning already."""
|
318
|
+
return (
|
319
|
+
name in _RESERVED_KEYWORDS
|
320
|
+
or name in self.gates
|
321
|
+
or name in itertools.chain.from_iterable(reversed(self.variables))
|
322
|
+
)
|
251
323
|
|
252
|
-
def
|
253
|
-
|
254
|
-
|
324
|
+
def can_shadow_symbol(self, name: str) -> bool:
|
325
|
+
"""Whether a new definition of this symbol can be made within the OpenQASM 3 shadowing
|
326
|
+
rules."""
|
327
|
+
return (
|
328
|
+
name not in self.variables[-1]
|
329
|
+
and name not in self.gates
|
330
|
+
and name not in _RESERVED_KEYWORDS
|
331
|
+
)
|
255
332
|
|
256
|
-
def
|
257
|
-
|
258
|
-
try:
|
259
|
-
# Registered gates.
|
260
|
-
return self._data[id(key)]
|
261
|
-
except KeyError:
|
262
|
-
pass
|
263
|
-
# Built-in gates.
|
264
|
-
if key.name not in self._data:
|
265
|
-
raise KeyError(key)
|
266
|
-
return key.name
|
267
|
-
return self._data[key]
|
268
|
-
|
269
|
-
def __iter__(self):
|
270
|
-
return iter(self._data)
|
271
|
-
|
272
|
-
def __contains__(self, instruction):
|
273
|
-
if isinstance(instruction, standard_gates.UGate):
|
274
|
-
return True
|
275
|
-
if id(instruction) in self._data:
|
276
|
-
return True
|
277
|
-
if self._data.get(instruction.name) is self.BASIS_GATE:
|
278
|
-
return True
|
279
|
-
if type(instruction) in [Gate, Instruction]: # user-defined instructions/gate
|
280
|
-
return self._data.get(instruction.name, None) == instruction
|
281
|
-
type_ = self._data.get(instruction.name)
|
282
|
-
if isinstance(type_, type) and isinstance(instruction, type_):
|
283
|
-
return True
|
284
|
-
return False
|
333
|
+
def escaped_declarable_name(self, name: str, *, allow_rename: bool, unique: bool = False):
|
334
|
+
"""Get an identifier based on ``name`` that can be safely shadowed within this scope.
|
285
335
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
336
|
+
If ``unique`` is ``True``, then the name is required to be unique across all live scopes,
|
337
|
+
not just able to be redefined."""
|
338
|
+
name_allowed = (
|
339
|
+
(lambda name: not self.symbol_defined(name)) if unique else self.can_shadow_symbol
|
340
|
+
)
|
341
|
+
valid_identifier = _VALID_DECLARABLE_IDENTIFIER
|
342
|
+
if allow_rename:
|
343
|
+
if not valid_identifier.fullmatch(name):
|
344
|
+
name = "_" + _BAD_IDENTIFIER_CHARACTERS.sub("_", name)
|
345
|
+
base = name
|
346
|
+
while not name_allowed(name):
|
347
|
+
name = f"{base}_{next(self._counter)}"
|
348
|
+
return name
|
349
|
+
if not valid_identifier.fullmatch(name):
|
350
|
+
raise QASM3ExporterError(f"cannot use '{name}' as a name; it is not a valid identifier")
|
351
|
+
if name in _RESERVED_KEYWORDS:
|
352
|
+
raise QASM3ExporterError(f"cannot use the keyword '{name}' as a variable name")
|
353
|
+
if not name_allowed(name):
|
354
|
+
if self.gates.get(name) is not None:
|
355
|
+
raise QASM3ExporterError(
|
356
|
+
f"cannot shadow variable '{name}', as it is already defined as a gate"
|
357
|
+
)
|
358
|
+
for scope in reversed(self.variables):
|
359
|
+
if (other := scope.get(name)) is not None:
|
360
|
+
break
|
361
|
+
else: # pragma: no cover
|
362
|
+
raise RuntimeError(f"internal error: could not locate unshadowable '{name}'")
|
363
|
+
raise QASM3ExporterError(
|
364
|
+
f"cannot shadow variable '{name}', as it is already defined as '{other}'"
|
365
|
+
)
|
366
|
+
return name
|
367
|
+
|
368
|
+
def register_variable(
|
369
|
+
self,
|
370
|
+
name: str,
|
371
|
+
variable: object,
|
372
|
+
*,
|
373
|
+
allow_rename: bool,
|
374
|
+
force_global: bool = False,
|
375
|
+
allow_hardware_qubit: bool = False,
|
376
|
+
) -> ast.Identifier:
|
377
|
+
"""Register a variable in the symbol table for the given scope, returning the name that
|
378
|
+
should be used to refer to the variable. The same name will be returned by subsequent calls
|
379
|
+
to :meth:`get_variable` within the same scope.
|
380
|
+
|
381
|
+
Args:
|
382
|
+
name: the name to base the identifier on.
|
383
|
+
variable: the Qiskit object this refers to. This can be ``None`` in the case of
|
384
|
+
reserving a dummy variable name that does not actually have a Qiskit object backing
|
385
|
+
it.
|
386
|
+
allow_rename: whether to allow the name to be mutated to escape it and/or make it safe
|
387
|
+
to define (avoiding keywords, subject to shadowing rules, etc).
|
388
|
+
force_global: force this declaration to be in the global scope.
|
389
|
+
allow_hardware_qubit: whether to allow hardware qubits to pass through as identifiers.
|
390
|
+
Hardware qubits are a dollar sign followed by a non-negative integer, and cannot be
|
391
|
+
declared, so are not suitable identifiers for most objects.
|
392
|
+
"""
|
393
|
+
scope_index = 0 if force_global else -1
|
394
|
+
# We still need to do this escaping and shadow checking if `force_global`, because we don't
|
395
|
+
# want a previous variable declared in the currently active scope to shadow the global.
|
396
|
+
# This logic would be cleaner if we made the naming choices later, after AST generation
|
397
|
+
# (e.g. by using only indices as the identifiers until we're outputting the program).
|
398
|
+
if allow_hardware_qubit and _VALID_HARDWARE_QUBIT.fullmatch(name):
|
399
|
+
if self.symbol_defined(name): # pragma: no cover
|
400
|
+
raise QASM3ExporterError(f"internal error: cannot redeclare hardware qubit {name}")
|
314
401
|
else:
|
315
|
-
|
316
|
-
|
402
|
+
name = self.escaped_declarable_name(
|
403
|
+
name, allow_rename=allow_rename, unique=force_global
|
404
|
+
)
|
405
|
+
identifier = ast.Identifier(name)
|
406
|
+
self.variables[scope_index][name] = variable
|
407
|
+
if variable is not None:
|
408
|
+
self.objects[scope_index][variable] = identifier
|
409
|
+
return identifier
|
317
410
|
|
411
|
+
def set_object_ident(self, ident: ast.Identifier, variable: object):
|
412
|
+
"""Set the identifier used to refer to a given object for this scope.
|
318
413
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
414
|
+
This overwrites any previously set identifier, such as during the original registration.
|
415
|
+
|
416
|
+
This is generally only useful for tracking "sub" objects, like bits out of a register, which
|
417
|
+
will have an `SubscriptedIdentifier` as their identifier."""
|
418
|
+
self.objects[-1][variable] = ident
|
419
|
+
|
420
|
+
def get_variable(self, variable: object) -> ast.Identifier:
|
421
|
+
"""Lookup a non-gate variable in the symbol table."""
|
422
|
+
for scope in reversed(self.objects):
|
423
|
+
if (out := scope.get(variable)) is not None:
|
424
|
+
return out
|
425
|
+
raise KeyError(f"'{variable}' is not defined in the current context")
|
426
|
+
|
427
|
+
def register_gate_without_definition(self, name: str, gate: Gate | None) -> ast.Identifier:
|
428
|
+
"""Register a gate that does not require an OQ3 definition.
|
429
|
+
|
430
|
+
If the ``gate`` is given, it will be used to validate that a call to it is compatible (such
|
431
|
+
as a known gate from an included file). If it is not given, it is treated as a user-defined
|
432
|
+
"basis gate" that assumes that all calling signatures are valid and that all gates of this
|
433
|
+
name are exactly compatible, which is somewhat dangerous."""
|
434
|
+
# Validate the name is usable.
|
435
|
+
name = self.escaped_declarable_name(name, allow_rename=False, unique=False)
|
436
|
+
ident = ast.Identifier(name)
|
437
|
+
if gate is None:
|
438
|
+
self.gates[name] = GateInfo(None, None)
|
439
|
+
else:
|
440
|
+
canonical = _gate_canonical_form(gate)
|
441
|
+
self.gates[name] = GateInfo(canonical, None)
|
442
|
+
if canonical._standard_gate is not None:
|
443
|
+
self.standard_gate_idents[canonical._standard_gate] = ident
|
444
|
+
else:
|
445
|
+
self.user_gate_idents[id(canonical)] = ident
|
446
|
+
return ident
|
447
|
+
|
448
|
+
def register_gate(
|
449
|
+
self,
|
450
|
+
name: str,
|
451
|
+
source: Gate,
|
452
|
+
params: Iterable[ast.Identifier],
|
453
|
+
qubits: Iterable[ast.Identifier],
|
454
|
+
body: ast.QuantumBlock,
|
455
|
+
) -> ast.Identifier:
|
456
|
+
"""Register the given gate in the symbol table, using the given components to build up the
|
457
|
+
full AST definition."""
|
458
|
+
name = self.escaped_declarable_name(name, allow_rename=True, unique=False)
|
459
|
+
ident = ast.Identifier(name)
|
460
|
+
self.gates[name] = GateInfo(
|
461
|
+
source, ast.QuantumGateDefinition(ident, tuple(params), tuple(qubits), body)
|
462
|
+
)
|
463
|
+
# Add the gate object with a magic lookup keep to the objects dictionary so we can retrieve
|
464
|
+
# it later. Standard gates are not guaranteed to have stable IDs (they're preferentially
|
465
|
+
# not even created in Python space), but user gates are.
|
466
|
+
if source._standard_gate is not None:
|
467
|
+
self.standard_gate_idents[source._standard_gate] = ident
|
468
|
+
else:
|
469
|
+
self.user_gate_idents[id(source)] = ident
|
470
|
+
return ident
|
471
|
+
|
472
|
+
def get_gate(self, gate: Gate) -> ast.Identifier | None:
|
473
|
+
"""Lookup the identifier for a given `Gate`, if it exists."""
|
474
|
+
canonical = _gate_canonical_form(gate)
|
475
|
+
# `our_defn.canonical is None` means a basis gate that we should assume is always valid.
|
476
|
+
if (our_defn := self.gates.get(gate.name)) is not None and (
|
477
|
+
our_defn.canonical is None or our_defn.canonical == canonical
|
478
|
+
):
|
479
|
+
return ast.Identifier(gate.name)
|
480
|
+
if canonical._standard_gate is not None:
|
481
|
+
if (our_ident := self.standard_gate_idents.get(canonical._standard_gate)) is None:
|
482
|
+
return None
|
483
|
+
return our_ident if self.gates[our_ident.string].canonical == canonical else None
|
484
|
+
# No need to check equality if we're looking up by `id`; we must have the same object.
|
485
|
+
return self.user_gate_idents.get(id(canonical))
|
486
|
+
|
487
|
+
|
488
|
+
def _gate_canonical_form(gate: Gate) -> Gate:
|
489
|
+
"""Get the canonical form of a gate.
|
490
|
+
|
491
|
+
This is the gate object that should be used to provide the OpenQASM 3 definition of a gate (but
|
492
|
+
not the call site; that's the input object). This lets us return a re-parametrised gate in
|
493
|
+
terms of general parameters, in cases where we can be sure that that is valid. This is
|
494
|
+
currently only Qiskit standard gates. This lets multiple call-site gates match the same symbol,
|
495
|
+
in the case of parametric gates.
|
496
|
+
|
497
|
+
The definition source provides the number of qubits, the parameter signature and the body of the
|
498
|
+
`gate` statement. It does not provide the name of the symbol being defined."""
|
499
|
+
# If a gate is part of the Qiskit standard-library gates, we know we can safely produce a
|
500
|
+
# reparameterised gate by passing the parameters positionally to the standard-gate constructor
|
501
|
+
# (and control state, if appropriate).
|
502
|
+
if gate._standard_gate and not isinstance(gate, ControlledGate):
|
503
|
+
return gate.base_class(*_FIXED_PARAMETERS[: len(gate.params)])
|
504
|
+
elif gate._standard_gate:
|
505
|
+
return gate.base_class(*_FIXED_PARAMETERS[: len(gate.params)], ctrl_state=gate.ctrl_state)
|
506
|
+
return gate
|
507
|
+
|
508
|
+
|
509
|
+
@dataclasses.dataclass
|
510
|
+
class BuildScope:
|
511
|
+
"""The structure used in the builder to store the contexts and re-mappings of bits from the
|
512
|
+
top-level scope where the bits were actually defined."""
|
513
|
+
|
514
|
+
circuit: QuantumCircuit
|
515
|
+
"""The circuit block that we're currently working on exporting."""
|
516
|
+
bit_map: dict[Bit, Bit]
|
517
|
+
"""Mapping of bit objects in ``circuit`` to the bit objects in the global-scope program
|
518
|
+
:class:`.QuantumCircuit` that they are bound to."""
|
327
519
|
|
328
520
|
|
329
521
|
class QASM3Builder:
|
@@ -344,13 +536,11 @@ class QASM3Builder:
|
|
344
536
|
allow_aliasing,
|
345
537
|
experimental=ExperimentalFeatures(0),
|
346
538
|
):
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
self.
|
352
|
-
self.push_context(quantumcircuit)
|
353
|
-
self.includeslist = includeslist
|
539
|
+
self.scope = BuildScope(
|
540
|
+
quantumcircuit,
|
541
|
+
{x: x for x in itertools.chain(quantumcircuit.qubits, quantumcircuit.clbits)},
|
542
|
+
)
|
543
|
+
self.symbols = SymbolTable()
|
354
544
|
# `_global_io_declarations` and `_global_classical_declarations` are stateful, and any
|
355
545
|
# operation that needs a parameter can append to them during the build. We make all
|
356
546
|
# classical declarations global because the IBM qe-compiler stack (our initial consumer of
|
@@ -359,110 +549,94 @@ class QASM3Builder:
|
|
359
549
|
# in the near term.
|
360
550
|
self._global_io_declarations = []
|
361
551
|
self._global_classical_forward_declarations = []
|
362
|
-
# An arbitrary counter to help with generation of unique ids for symbol names when there are
|
363
|
-
# clashes (though we generally prefer to keep user names if possible).
|
364
|
-
self._counter = itertools.count()
|
365
552
|
self.disable_constants = disable_constants
|
366
553
|
self.allow_aliasing = allow_aliasing
|
367
|
-
self.
|
554
|
+
self.includes = includeslist
|
555
|
+
self.basis_gates = basis_gates
|
368
556
|
self.experimental = experimental
|
369
557
|
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
self.global_namespace.register(instruction)
|
382
|
-
|
383
|
-
def _register_variable(self, variable, scope: _Scope, name=None) -> ast.Identifier:
|
384
|
-
"""Register a variable in the symbol table for the given scope, returning the name that
|
385
|
-
should be used to refer to the variable. The same name will be returned by subsequent calls
|
386
|
-
to :meth:`_lookup_variable` within the same scope.
|
387
|
-
|
388
|
-
If ``name`` is given explicitly, it must not already be defined in the scope.
|
389
|
-
"""
|
390
|
-
# Note that the registration only checks for the existence of a variable that was declared
|
391
|
-
# in the current scope, not just one that's available. This is a rough implementation of
|
392
|
-
# the shadowing proposal currently being drafted for OpenQASM 3, though we expect it to be
|
393
|
-
# expanded and modified in the future (2022-03-07).
|
394
|
-
table = scope.symbol_map
|
395
|
-
if name is not None:
|
396
|
-
if name in _RESERVED_KEYWORDS:
|
397
|
-
raise QASM3ExporterError(f"cannot reserve the keyword '{name}' as a variable name")
|
398
|
-
if name in table:
|
399
|
-
raise QASM3ExporterError(
|
400
|
-
f"tried to reserve '{name}', but it is already used by '{table[name]}'"
|
401
|
-
)
|
402
|
-
if self.global_namespace.has_symbol(name):
|
403
|
-
raise QASM3ExporterError(
|
404
|
-
f"tried to reserve '{name}', but it is already used by a gate"
|
405
|
-
)
|
406
|
-
else:
|
407
|
-
name = self._unique_name(variable.name, scope)
|
408
|
-
identifier = ast.Identifier(name)
|
409
|
-
table[identifier.string] = variable
|
410
|
-
table[variable] = identifier
|
411
|
-
return identifier
|
412
|
-
|
413
|
-
def _reserve_variable_name(self, name: ast.Identifier, scope: _Scope) -> ast.Identifier:
|
414
|
-
"""Reserve a variable name in the given scope, raising a :class:`.QASM3ExporterError` if
|
415
|
-
the name is already in use.
|
416
|
-
|
417
|
-
This is useful for autogenerated names that the exporter itself reserves when dealing with
|
418
|
-
objects that have no standard Terra object backing them.
|
419
|
-
|
420
|
-
Returns the same identifier, for convenience in chaining."""
|
421
|
-
table = scope.symbol_map
|
422
|
-
if name.string in table:
|
423
|
-
variable = table[name.string]
|
424
|
-
raise QASM3ExporterError(
|
425
|
-
f"tried to reserve '{name.string}', but it is already used by '{variable}'"
|
558
|
+
@contextlib.contextmanager
|
559
|
+
def new_scope(self, circuit: QuantumCircuit, qubits: Iterable[Qubit], clbits: Iterable[Clbit]):
|
560
|
+
"""Context manager that pushes a new scope (like a ``for`` or ``while`` loop body) onto the
|
561
|
+
current context stack."""
|
562
|
+
current_map = self.scope.bit_map
|
563
|
+
qubits = tuple(current_map[qubit] for qubit in qubits)
|
564
|
+
clbits = tuple(current_map[clbit] for clbit in clbits)
|
565
|
+
if circuit.num_qubits != len(qubits):
|
566
|
+
raise QASM3ExporterError( # pragma: no cover
|
567
|
+
f"Tried to push a scope whose circuit needs {circuit.num_qubits} qubits, but only"
|
568
|
+
f" provided {len(qubits)} qubits to create the mapping."
|
426
569
|
)
|
427
|
-
|
428
|
-
|
570
|
+
if circuit.num_clbits != len(clbits):
|
571
|
+
raise QASM3ExporterError( # pragma: no cover
|
572
|
+
f"Tried to push a scope whose circuit needs {circuit.num_clbits} clbits, but only"
|
573
|
+
f" provided {len(clbits)} clbits to create the mapping."
|
574
|
+
)
|
575
|
+
mapping = dict(itertools.chain(zip(circuit.qubits, qubits), zip(circuit.clbits, clbits)))
|
576
|
+
self.symbols.push_scope()
|
577
|
+
old_scope, self.scope = self.scope, BuildScope(circuit, mapping)
|
578
|
+
yield self.scope
|
579
|
+
self.scope = old_scope
|
580
|
+
self.symbols.pop_scope()
|
581
|
+
|
582
|
+
@contextlib.contextmanager
|
583
|
+
def new_context(self, body: QuantumCircuit):
|
584
|
+
"""Push a new context (like for a ``gate`` or ``def`` body) onto the stack."""
|
585
|
+
mapping = {bit: bit for bit in itertools.chain(body.qubits, body.clbits)}
|
586
|
+
|
587
|
+
old_symbols, self.symbols = self.symbols, self.symbols.new_context()
|
588
|
+
old_scope, self.scope = self.scope, BuildScope(body, mapping)
|
589
|
+
yield self.scope
|
590
|
+
self.scope = old_scope
|
591
|
+
self.symbols = old_symbols
|
429
592
|
|
430
593
|
def _lookup_variable(self, variable) -> ast.Identifier:
|
431
|
-
"""Lookup a
|
432
|
-
to represent it in OpenQASM 3 programmes."""
|
594
|
+
"""Lookup a Qiskit object within the current context, and return the name that should be
|
595
|
+
used to represent it in OpenQASM 3 programmes."""
|
433
596
|
if isinstance(variable, Bit):
|
434
|
-
variable = self.
|
435
|
-
|
436
|
-
if variable in scope.symbol_map:
|
437
|
-
return scope.symbol_map[variable]
|
438
|
-
raise KeyError(f"'{variable}' is not defined in the current context")
|
439
|
-
|
440
|
-
def build_header(self):
|
441
|
-
"""Builds a Header"""
|
442
|
-
version = ast.Version("3.0")
|
443
|
-
includes = self.build_includes()
|
444
|
-
return ast.Header(version, includes)
|
597
|
+
variable = self.scope.bit_map[variable]
|
598
|
+
return self.symbols.get_variable(variable)
|
445
599
|
|
446
600
|
def build_program(self):
|
447
601
|
"""Builds a Program"""
|
448
|
-
circuit = self.
|
602
|
+
circuit = self.scope.circuit
|
449
603
|
if circuit.num_captured_vars:
|
450
604
|
raise QASM3ExporterError(
|
451
605
|
"cannot export an inner scope with captured variables as a top-level program"
|
452
606
|
)
|
453
|
-
header = self.build_header()
|
454
607
|
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
608
|
+
# The order we build parts of the AST has an effect on which names will get escaped to avoid
|
609
|
+
# collisions. The current ideas are:
|
610
|
+
#
|
611
|
+
# * standard-library include files _must_ define symbols of the correct name.
|
612
|
+
# * classical registers, IO variables and `Var` nodes are likely to be referred to by name
|
613
|
+
# by a user, so they get very high priority - we search for them before doing anything.
|
614
|
+
# * qubit registers are not typically referred to by name by users, so they get a lower
|
615
|
+
# priority than the classical variables.
|
616
|
+
# * we often have to escape user-defined gate names anyway because of our dodgy parameter
|
617
|
+
# handling, so they get the lowest priority; they get defined as they are encountered.
|
618
|
+
#
|
619
|
+
# An alternative approach would be to defer naming decisions until we are outputting the
|
620
|
+
# AST, and using some UUID for each symbol we're going to define in the interrim. This
|
621
|
+
# would require relatively large changes to the symbol-table and AST handling, however.
|
622
|
+
|
623
|
+
for builtin, gate in _BUILTIN_GATES.items():
|
624
|
+
self.symbols.register_gate_without_definition(builtin, gate)
|
625
|
+
for builtin in self.basis_gates:
|
626
|
+
if builtin in _BUILTIN_GATES:
|
627
|
+
# It's built into the langauge; we don't need to re-add it.
|
628
|
+
continue
|
629
|
+
try:
|
630
|
+
self.symbols.register_gate_without_definition(builtin, None)
|
631
|
+
except QASM3ExporterError as exc:
|
632
|
+
raise QASM3ExporterError(
|
633
|
+
f"Cannot use '{builtin}' as a basis gate for the reason in the prior exception."
|
634
|
+
" Consider renaming the gate if needed, or omitting this basis gate if not."
|
635
|
+
) from exc
|
464
636
|
|
465
|
-
|
637
|
+
header = ast.Header(ast.Version("3.0"), list(self.build_includes()))
|
638
|
+
|
639
|
+
# Early IBM runtime parametrization uses unbound `Parameter` instances as `input` variables,
|
466
640
|
# not the explicit realtime `Var` variables, so we need this explicit scan.
|
467
641
|
self.hoist_global_parameter_declarations()
|
468
642
|
# Qiskit's clbits and classical registers need to get mapped to implicit OQ3 variables, but
|
@@ -476,9 +650,11 @@ class QASM3Builder:
|
|
476
650
|
|
477
651
|
# Similarly, QuantumCircuit qubits/registers are only new variables in the global scope.
|
478
652
|
quantum_declarations = self.build_quantum_declarations()
|
653
|
+
|
479
654
|
# This call has side-effects - it can populate `self._global_io_declarations` and
|
480
655
|
# `self._global_classical_declarations` as a courtesy to the qe-compiler that prefers our
|
481
|
-
# hacky temporary `switch` target variables to be globally defined.
|
656
|
+
# hacky temporary `switch` target variables to be globally defined. It also populates the
|
657
|
+
# symbol table with encountered gates that weren't previously defined.
|
482
658
|
main_statements = self.build_current_scope()
|
483
659
|
|
484
660
|
statements = [
|
@@ -487,8 +663,7 @@ class QASM3Builder:
|
|
487
663
|
# In older versions of the reference OQ3 grammar, IO declarations had to come before
|
488
664
|
# anything else, so we keep doing that as a courtesy.
|
489
665
|
self._global_io_declarations,
|
490
|
-
|
491
|
-
gate_definitions,
|
666
|
+
(gate.node for gate in self.symbols.gates.values() if gate.node is not None),
|
492
667
|
self._global_classical_forward_declarations,
|
493
668
|
quantum_declarations,
|
494
669
|
main_statements,
|
@@ -497,172 +672,98 @@ class QASM3Builder:
|
|
497
672
|
]
|
498
673
|
return ast.Program(header, statements)
|
499
674
|
|
500
|
-
def hoist_declarations(self, instructions, *, opaques, gates):
|
501
|
-
"""Walks the definitions in gates/instructions to make a list of gates to declare.
|
502
|
-
|
503
|
-
Mutates ``opaques`` and ``gates`` in-place if given, and returns them."""
|
504
|
-
for instruction in instructions:
|
505
|
-
if isinstance(instruction.operation, ControlFlowOp):
|
506
|
-
for block in instruction.operation.blocks:
|
507
|
-
self.hoist_declarations(block.data, opaques=opaques, gates=gates)
|
508
|
-
continue
|
509
|
-
if instruction.operation in self.global_namespace or isinstance(
|
510
|
-
instruction.operation, self.builtins
|
511
|
-
):
|
512
|
-
continue
|
513
|
-
|
514
|
-
if isinstance(instruction.operation, standard_gates.CXGate):
|
515
|
-
# CX gets super duper special treatment because it's the base of Terra's definition
|
516
|
-
# tree, but isn't an OQ3 built-in. We use `isinstance` because we haven't fully
|
517
|
-
# fixed what the name/class distinction is (there's a test from the original OQ3
|
518
|
-
# exporter that tries a naming collision with 'cx').
|
519
|
-
self._register_gate(instruction.operation)
|
520
|
-
gates.append(instruction.operation)
|
521
|
-
elif instruction.operation.definition is None:
|
522
|
-
self._register_opaque(instruction.operation)
|
523
|
-
opaques.append(instruction.operation)
|
524
|
-
elif not isinstance(instruction.operation, Gate):
|
525
|
-
raise QASM3ExporterError("Exporting non-unitary instructions is not yet supported.")
|
526
|
-
else:
|
527
|
-
self.hoist_declarations(
|
528
|
-
instruction.operation.definition.data, opaques=opaques, gates=gates
|
529
|
-
)
|
530
|
-
self._register_gate(instruction.operation)
|
531
|
-
gates.append(instruction.operation)
|
532
|
-
return opaques, gates
|
533
|
-
|
534
|
-
def global_scope(self, assert_=False):
|
535
|
-
"""Return the global circuit scope that is used as the basis of the full program. If
|
536
|
-
``assert_=True``, then this raises :obj:`.QASM3ExporterError` if the current context is not
|
537
|
-
the global one."""
|
538
|
-
if assert_ and len(self._circuit_ctx) != 1 and len(self._circuit_ctx[0]) != 1:
|
539
|
-
# Defensive code to help catch logic errors.
|
540
|
-
raise QASM3ExporterError( # pragma: no cover
|
541
|
-
f"Not currently in the global context. Current contexts are: {self._circuit_ctx}"
|
542
|
-
)
|
543
|
-
return self._circuit_ctx[0][0]
|
544
|
-
|
545
|
-
def current_scope(self):
|
546
|
-
"""Return the current circuit scope."""
|
547
|
-
return self._circuit_ctx[-1][-1]
|
548
|
-
|
549
|
-
def current_context(self):
|
550
|
-
"""Return the current context (list of scopes)."""
|
551
|
-
return self._circuit_ctx[-1]
|
552
|
-
|
553
|
-
def push_scope(self, circuit: QuantumCircuit, qubits: Iterable[Qubit], clbits: Iterable[Clbit]):
|
554
|
-
"""Push a new scope (like a ``for`` or ``while`` loop body) onto the current context
|
555
|
-
stack."""
|
556
|
-
current_map = self.current_scope().bit_map
|
557
|
-
qubits = tuple(current_map[qubit] for qubit in qubits)
|
558
|
-
clbits = tuple(current_map[clbit] for clbit in clbits)
|
559
|
-
if circuit.num_qubits != len(qubits):
|
560
|
-
raise QASM3ExporterError( # pragma: no cover
|
561
|
-
f"Tried to push a scope whose circuit needs {circuit.num_qubits} qubits, but only"
|
562
|
-
f" provided {len(qubits)} qubits to create the mapping."
|
563
|
-
)
|
564
|
-
if circuit.num_clbits != len(clbits):
|
565
|
-
raise QASM3ExporterError( # pragma: no cover
|
566
|
-
f"Tried to push a scope whose circuit needs {circuit.num_clbits} clbits, but only"
|
567
|
-
f" provided {len(clbits)} clbits to create the mapping."
|
568
|
-
)
|
569
|
-
mapping = dict(itertools.chain(zip(circuit.qubits, qubits), zip(circuit.clbits, clbits)))
|
570
|
-
self.current_context().append(_Scope(circuit, mapping, {}))
|
571
|
-
|
572
|
-
def pop_scope(self) -> _Scope:
|
573
|
-
"""Pop the current scope (like a ``for`` or ``while`` loop body) off the current context
|
574
|
-
stack."""
|
575
|
-
if len(self._circuit_ctx[-1]) <= 1:
|
576
|
-
raise QASM3ExporterError( # pragma: no cover
|
577
|
-
"Tried to pop a scope from the current context, but there are no current scopes."
|
578
|
-
)
|
579
|
-
return self._circuit_ctx[-1].pop()
|
580
|
-
|
581
|
-
def push_context(self, outer_context: QuantumCircuit):
|
582
|
-
"""Push a new context (like for a ``gate`` or ``def`` body) onto the stack."""
|
583
|
-
mapping = {bit: bit for bit in itertools.chain(outer_context.qubits, outer_context.clbits)}
|
584
|
-
self._circuit_ctx.append([_Scope(outer_context, mapping, {})])
|
585
|
-
|
586
|
-
def pop_context(self):
|
587
|
-
"""Pop the current context (like for a ``gate`` or ``def`` body) onto the stack."""
|
588
|
-
if len(self._circuit_ctx) == 1:
|
589
|
-
raise QASM3ExporterError( # pragma: no cover
|
590
|
-
"Tried to pop the current context, but that is the global context."
|
591
|
-
)
|
592
|
-
if len(self._circuit_ctx[-1]) != 1:
|
593
|
-
raise QASM3ExporterError( # pragma: no cover
|
594
|
-
"Tried to pop the current context while there are still"
|
595
|
-
f" {len(self._circuit_ctx[-1]) - 1} unclosed scopes."
|
596
|
-
)
|
597
|
-
self._circuit_ctx.pop()
|
598
|
-
|
599
675
|
def build_includes(self):
|
600
676
|
"""Builds a list of included files."""
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
#
|
616
|
-
#
|
617
|
-
#
|
618
|
-
# exporter that tries a naming collision with 'cx').
|
677
|
+
for filename in self.includes:
|
678
|
+
if (definitions := _KNOWN_INCLUDES.get(filename)) is None:
|
679
|
+
raise QASM3ExporterError(f"Unknown OpenQASM 3 include file: '{filename}'")
|
680
|
+
for name, gate in definitions.items():
|
681
|
+
self.symbols.register_gate_without_definition(name, gate)
|
682
|
+
yield ast.Include(filename)
|
683
|
+
|
684
|
+
def define_gate(self, gate: Gate) -> ast.Identifier:
|
685
|
+
"""Define a gate in the symbol table, including building the gate-definition statement for
|
686
|
+
it.
|
687
|
+
|
688
|
+
This recurses through gate-definition statements."""
|
689
|
+
if issubclass(gate.base_class, library.CXGate) and gate.ctrl_state == 1:
|
690
|
+
# CX gets super duper special treatment because it's the base of Qiskit's definition
|
691
|
+
# tree, but isn't an OQ3 built-in (it was in OQ2). We use `issubclass` because we
|
692
|
+
# haven't fully fixed what the name/class distinction is (there's a test from the
|
693
|
+
# original OQ3 exporter that tries a naming collision with 'cx').
|
619
694
|
control, target = ast.Identifier("c"), ast.Identifier("t")
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
695
|
+
body = ast.QuantumBlock(
|
696
|
+
[
|
697
|
+
ast.QuantumGateCall(
|
698
|
+
self.symbols.get_gate(library.UGate(math.pi, 0, math.pi)),
|
699
|
+
[control, target],
|
700
|
+
parameters=[ast.Constant.PI, ast.IntegerLiteral(0), ast.Constant.PI],
|
701
|
+
modifiers=[ast.QuantumGateModifier(ast.QuantumGateModifierName.CTRL)],
|
702
|
+
)
|
703
|
+
]
|
629
704
|
)
|
705
|
+
return self.symbols.register_gate(gate.name, gate, (), (control, target), body)
|
706
|
+
if gate.definition is None:
|
707
|
+
raise QASM3ExporterError(f"failed to export gate '{gate.name}' that has no definition")
|
708
|
+
canonical = _gate_canonical_form(gate)
|
709
|
+
with self.new_context(canonical.definition):
|
710
|
+
defn = self.scope.circuit
|
711
|
+
# If `defn.num_parameters == 0` but `gate.params` is non-empty, we are likely in the
|
712
|
+
# case where the gate's circuit definition is fully bound (so we can't detect its inputs
|
713
|
+
# anymore). This is a problem in our data model - for arbitrary user gates, there's no
|
714
|
+
# way we can reliably get a parametric version of the gate through our interfaces. In
|
715
|
+
# this case, we output a gate that has dummy parameters, and rely on it being a
|
716
|
+
# different `id` each time to avoid duplication. We assume that the parametrisation
|
717
|
+
# order matches (which is a _big_ assumption).
|
718
|
+
#
|
719
|
+
# If `defn.num_parameters > 0`, we enforce that it must match how it's called.
|
720
|
+
if defn.num_parameters > 0:
|
721
|
+
if defn.num_parameters != len(gate.params):
|
722
|
+
raise QASM3ExporterError(
|
723
|
+
"parameter mismatch in definition of '{gate}':"
|
724
|
+
f" call has {len(gate.params)}, definition has {defn.num_parameters}"
|
725
|
+
)
|
726
|
+
params = [
|
727
|
+
self.symbols.register_variable(param.name, param, allow_rename=True)
|
728
|
+
for param in defn.parameters
|
729
|
+
]
|
730
|
+
else:
|
731
|
+
# Fill with dummy parameters. The name is unimportant, because they're not actually
|
732
|
+
# used in the definition.
|
733
|
+
params = [
|
734
|
+
self.symbols.register_variable(
|
735
|
+
f"{self.gate_parameter_prefix}_{i}", None, allow_rename=True
|
736
|
+
)
|
737
|
+
for i in range(len(gate.params))
|
738
|
+
]
|
739
|
+
qubits = [
|
740
|
+
self.symbols.register_variable(
|
741
|
+
f"{self.gate_qubit_prefix}_{i}", qubit, allow_rename=True
|
742
|
+
)
|
743
|
+
for i, qubit in enumerate(defn.qubits)
|
744
|
+
]
|
745
|
+
body = ast.QuantumBlock(self.build_current_scope())
|
746
|
+
# We register the gate only after building its body so that any gates we needed for that in
|
747
|
+
# turn are registered in the correct order. Gates can't be recursive in OQ3, so there's no
|
748
|
+
# problem with delaying this.
|
749
|
+
return self.symbols.register_gate(canonical.name, canonical, params, qubits, body)
|
630
750
|
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
return ast.QuantumGateDefinition(signature, body)
|
636
|
-
|
637
|
-
def build_gate_signature(self, gate):
|
638
|
-
"""Builds a QuantumGateSignature"""
|
639
|
-
name = self.global_namespace[gate]
|
640
|
-
params = []
|
641
|
-
definition = gate.definition
|
642
|
-
# Dummy parameters
|
643
|
-
scope = self.current_scope()
|
644
|
-
for num in range(len(gate.params) - len(definition.parameters)):
|
645
|
-
param_name = f"{self.gate_parameter_prefix}_{num}"
|
646
|
-
params.append(self._reserve_variable_name(ast.Identifier(param_name), scope))
|
647
|
-
params += [self._register_variable(param, scope) for param in definition.parameters]
|
648
|
-
quantum_arguments = [
|
649
|
-
self._register_variable(
|
650
|
-
qubit, scope, self._unique_name(f"{self.gate_qubit_prefix}_{i}", scope)
|
651
|
-
)
|
652
|
-
for i, qubit in enumerate(definition.qubits)
|
653
|
-
]
|
654
|
-
return ast.QuantumGateSignature(ast.Identifier(name), quantum_arguments, params or None)
|
751
|
+
def assert_global_scope(self):
|
752
|
+
"""Raise an error if we are not in the global scope, as a defensive measure."""
|
753
|
+
if len(self.symbols.variables) > 1: # pragma: no cover
|
754
|
+
raise RuntimeError("not currently in the global scope")
|
655
755
|
|
656
756
|
def hoist_global_parameter_declarations(self):
|
657
757
|
"""Extend ``self._global_io_declarations`` and ``self._global_classical_declarations`` with
|
658
758
|
any implicit declarations used to support the early IBM efforts to use :class:`.Parameter`
|
659
759
|
as an input variable."""
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
760
|
+
self.assert_global_scope()
|
761
|
+
circuit = self.scope.circuit
|
762
|
+
for parameter in circuit.parameters:
|
763
|
+
parameter_name = self.symbols.register_variable(
|
764
|
+
parameter.name, parameter, allow_rename=True
|
665
765
|
)
|
766
|
+
declaration = _infer_variable_declaration(circuit, parameter, parameter_name)
|
666
767
|
if declaration is None:
|
667
768
|
continue
|
668
769
|
if isinstance(declaration, ast.IODeclaration):
|
@@ -676,15 +777,16 @@ class QASM3Builder:
|
|
676
777
|
doesn't involve the declaration of *new* bits or registers in inner scopes; only the
|
677
778
|
:class:`.expr.Var` mechanism allows that.
|
678
779
|
|
679
|
-
The
|
780
|
+
The behavior of this function depends on the setting ``allow_aliasing``. If this
|
680
781
|
is ``True``, then the output will be in the same form as the output of
|
681
782
|
:meth:`.build_classical_declarations`, with the registers being aliases. If ``False``, it
|
682
783
|
will instead return a :obj:`.ast.ClassicalDeclaration` for each classical register, and one
|
683
784
|
for the loose :obj:`.Clbit` instances, and will raise :obj:`QASM3ExporterError` if any
|
684
785
|
registers overlap.
|
685
786
|
"""
|
686
|
-
|
687
|
-
|
787
|
+
self.assert_global_scope()
|
788
|
+
circuit = self.scope.circuit
|
789
|
+
if any(len(circuit.find_bit(q).registers) > 1 for q in circuit.clbits):
|
688
790
|
# There are overlapping registers, so we need to use aliases to emit the structure.
|
689
791
|
if not self.allow_aliasing:
|
690
792
|
raise QASM3ExporterError(
|
@@ -694,34 +796,32 @@ class QASM3Builder:
|
|
694
796
|
clbits = (
|
695
797
|
ast.ClassicalDeclaration(
|
696
798
|
ast.BitType(),
|
697
|
-
self.
|
698
|
-
|
799
|
+
self.symbols.register_variable(
|
800
|
+
f"{self.loose_bit_prefix}{i}", clbit, allow_rename=True
|
699
801
|
),
|
700
802
|
)
|
701
|
-
for i, clbit in enumerate(
|
803
|
+
for i, clbit in enumerate(circuit.clbits)
|
702
804
|
)
|
703
805
|
self._global_classical_forward_declarations.extend(clbits)
|
704
|
-
self._global_classical_forward_declarations.extend(
|
705
|
-
self.build_aliases(scope.circuit.cregs)
|
706
|
-
)
|
806
|
+
self._global_classical_forward_declarations.extend(self.build_aliases(circuit.cregs))
|
707
807
|
return
|
708
808
|
# If we're here, we're in the clbit happy path where there are no clbits that are in more
|
709
809
|
# than one register. We can output things very naturally.
|
710
810
|
self._global_classical_forward_declarations.extend(
|
711
811
|
ast.ClassicalDeclaration(
|
712
812
|
ast.BitType(),
|
713
|
-
self.
|
714
|
-
|
813
|
+
self.symbols.register_variable(
|
814
|
+
f"{self.loose_bit_prefix}{i}", clbit, allow_rename=True
|
715
815
|
),
|
716
816
|
)
|
717
|
-
for i, clbit in enumerate(
|
718
|
-
if not
|
817
|
+
for i, clbit in enumerate(circuit.clbits)
|
818
|
+
if not circuit.find_bit(clbit).registers
|
719
819
|
)
|
720
|
-
for register in
|
721
|
-
name = self.
|
820
|
+
for register in circuit.cregs:
|
821
|
+
name = self.symbols.register_variable(register.name, register, allow_rename=True)
|
722
822
|
for i, bit in enumerate(register):
|
723
|
-
|
724
|
-
name.string, ast.IntegerLiteral(i)
|
823
|
+
self.symbols.set_object_ident(
|
824
|
+
ast.SubscriptedIdentifier(name.string, ast.IntegerLiteral(i)), bit
|
725
825
|
)
|
726
826
|
self._global_classical_forward_declarations.append(
|
727
827
|
ast.ClassicalDeclaration(ast.BitArrayType(len(register)), name)
|
@@ -733,27 +833,31 @@ class QASM3Builder:
|
|
733
833
|
Local :class:`.expr.Var` declarations are handled by the regular local-block scope builder,
|
734
834
|
and the :class:`.QuantumCircuit` data model ensures that the only time an IO variable can
|
735
835
|
occur is in an outermost block."""
|
736
|
-
|
737
|
-
|
836
|
+
self.assert_global_scope()
|
837
|
+
circuit = self.scope.circuit
|
838
|
+
for var in circuit.iter_input_vars():
|
738
839
|
self._global_io_declarations.append(
|
739
840
|
ast.IODeclaration(
|
740
841
|
ast.IOModifier.INPUT,
|
741
842
|
_build_ast_type(var.type),
|
742
|
-
self.
|
843
|
+
self.symbols.register_variable(var.name, var, allow_rename=True),
|
743
844
|
)
|
744
845
|
)
|
745
846
|
|
746
847
|
def build_quantum_declarations(self):
|
747
848
|
"""Return a list of AST nodes declaring all the qubits in the current scope, and all the
|
748
849
|
alias declarations for these qubits."""
|
749
|
-
|
750
|
-
|
850
|
+
self.assert_global_scope()
|
851
|
+
circuit = self.scope.circuit
|
852
|
+
if circuit.layout is not None:
|
751
853
|
# We're referring to physical qubits. These can't be declared in OQ3, but we need to
|
752
854
|
# track the bit -> expression mapping in our symbol table.
|
753
|
-
for i, bit in enumerate(
|
754
|
-
|
855
|
+
for i, bit in enumerate(circuit.qubits):
|
856
|
+
self.symbols.register_variable(
|
857
|
+
f"${i}", bit, allow_rename=False, allow_hardware_qubit=True
|
858
|
+
)
|
755
859
|
return []
|
756
|
-
if any(len(
|
860
|
+
if any(len(circuit.find_bit(q).registers) > 1 for q in circuit.qubits):
|
757
861
|
# There are overlapping registers, so we need to use aliases to emit the structure.
|
758
862
|
if not self.allow_aliasing:
|
759
863
|
raise QASM3ExporterError(
|
@@ -762,30 +866,30 @@ class QASM3Builder:
|
|
762
866
|
)
|
763
867
|
qubits = [
|
764
868
|
ast.QuantumDeclaration(
|
765
|
-
self.
|
766
|
-
|
869
|
+
self.symbols.register_variable(
|
870
|
+
f"{self.loose_qubit_prefix}{i}", qubit, allow_rename=True
|
767
871
|
)
|
768
872
|
)
|
769
|
-
for i, qubit in enumerate(
|
873
|
+
for i, qubit in enumerate(circuit.qubits)
|
770
874
|
]
|
771
|
-
return qubits + self.build_aliases(
|
875
|
+
return qubits + self.build_aliases(circuit.qregs)
|
772
876
|
# If we're here, we're in the virtual-qubit happy path where there are no qubits that are in
|
773
877
|
# more than one register. We can output things very naturally.
|
774
878
|
loose_qubits = [
|
775
879
|
ast.QuantumDeclaration(
|
776
|
-
self.
|
777
|
-
|
880
|
+
self.symbols.register_variable(
|
881
|
+
f"{self.loose_qubit_prefix}{i}", qubit, allow_rename=True
|
778
882
|
)
|
779
883
|
)
|
780
|
-
for i, qubit in enumerate(
|
781
|
-
if not
|
884
|
+
for i, qubit in enumerate(circuit.qubits)
|
885
|
+
if not circuit.find_bit(qubit).registers
|
782
886
|
]
|
783
887
|
registers = []
|
784
|
-
for register in
|
785
|
-
name = self.
|
888
|
+
for register in circuit.qregs:
|
889
|
+
name = self.symbols.register_variable(register.name, register, allow_rename=True)
|
786
890
|
for i, bit in enumerate(register):
|
787
|
-
|
788
|
-
name.string, ast.IntegerLiteral(i)
|
891
|
+
self.symbols.set_object_ident(
|
892
|
+
ast.SubscriptedIdentifier(name.string, ast.IntegerLiteral(i)), bit
|
789
893
|
)
|
790
894
|
registers.append(
|
791
895
|
ast.QuantumDeclaration(name, ast.Designator(ast.IntegerLiteral(len(register))))
|
@@ -795,15 +899,14 @@ class QASM3Builder:
|
|
795
899
|
def build_aliases(self, registers: Iterable[Register]) -> List[ast.AliasStatement]:
|
796
900
|
"""Return a list of alias declarations for the given registers. The registers can be either
|
797
901
|
classical or quantum."""
|
798
|
-
scope = self.current_scope()
|
799
902
|
out = []
|
800
903
|
for register in registers:
|
801
|
-
name = self.
|
904
|
+
name = self.symbols.register_variable(register.name, register, allow_rename=True)
|
802
905
|
elements = [self._lookup_variable(bit) for bit in register]
|
803
906
|
for i, bit in enumerate(register):
|
804
907
|
# This might shadow previous definitions, but that's not a problem.
|
805
|
-
|
806
|
-
name.string, ast.IntegerLiteral(i)
|
908
|
+
self.symbols.set_object_ident(
|
909
|
+
ast.SubscriptedIdentifier(name.string, ast.IntegerLiteral(i)), bit
|
807
910
|
)
|
808
911
|
out.append(ast.AliasStatement(name, ast.IndexSet(elements)))
|
809
912
|
return out
|
@@ -814,7 +917,6 @@ class QASM3Builder:
|
|
814
917
|
In addition to everything literally in the circuit's ``data`` field, this also includes
|
815
918
|
declarations for any local :class:`.expr.Var` nodes.
|
816
919
|
"""
|
817
|
-
scope = self.current_scope()
|
818
920
|
|
819
921
|
# We forward-declare all local variables uninitialised at the top of their scope. It would
|
820
922
|
# be nice to declare the variable at the point of first store (so we can write things like
|
@@ -824,10 +926,13 @@ class QASM3Builder:
|
|
824
926
|
# variable, or the initial write to a variable is within a control-flow scope. (It would be
|
825
927
|
# easier to see the def/use chain needed to do this cleanly if we were using `DAGCircuit`.)
|
826
928
|
statements = [
|
827
|
-
ast.ClassicalDeclaration(
|
828
|
-
|
929
|
+
ast.ClassicalDeclaration(
|
930
|
+
_build_ast_type(var.type),
|
931
|
+
self.symbols.register_variable(var.name, var, allow_rename=True),
|
932
|
+
)
|
933
|
+
for var in self.scope.circuit.iter_declared_vars()
|
829
934
|
]
|
830
|
-
for instruction in scope.circuit.data:
|
935
|
+
for instruction in self.scope.circuit.data:
|
831
936
|
if isinstance(instruction.operation, ControlFlowOp):
|
832
937
|
if isinstance(instruction.operation, ForLoopOp):
|
833
938
|
statements.append(self.build_for_loop(instruction))
|
@@ -871,7 +976,10 @@ class QASM3Builder:
|
|
871
976
|
elif isinstance(instruction.operation, ContinueLoopOp):
|
872
977
|
nodes = [ast.ContinueStatement()]
|
873
978
|
else:
|
874
|
-
|
979
|
+
raise QASM3ExporterError(
|
980
|
+
"non-unitary subroutine calls are not yet supported,"
|
981
|
+
f" but received '{instruction.operation}'"
|
982
|
+
)
|
875
983
|
|
876
984
|
if instruction.operation.condition is None:
|
877
985
|
statements.extend(nodes)
|
@@ -890,24 +998,21 @@ class QASM3Builder:
|
|
890
998
|
condition = self.build_expression(_lift_condition(instruction.operation.condition))
|
891
999
|
|
892
1000
|
true_circuit = instruction.operation.blocks[0]
|
893
|
-
self.
|
894
|
-
|
895
|
-
self.pop_scope()
|
1001
|
+
with self.new_scope(true_circuit, instruction.qubits, instruction.clbits):
|
1002
|
+
true_body = ast.ProgramBlock(self.build_current_scope())
|
896
1003
|
if len(instruction.operation.blocks) == 1:
|
897
1004
|
return ast.BranchingStatement(condition, true_body, None)
|
898
1005
|
|
899
1006
|
false_circuit = instruction.operation.blocks[1]
|
900
|
-
self.
|
901
|
-
|
902
|
-
self.pop_scope()
|
1007
|
+
with self.new_scope(false_circuit, instruction.qubits, instruction.clbits):
|
1008
|
+
false_body = ast.ProgramBlock(self.build_current_scope())
|
903
1009
|
return ast.BranchingStatement(condition, true_body, false_body)
|
904
1010
|
|
905
1011
|
def build_switch_statement(self, instruction: CircuitInstruction) -> Iterable[ast.Statement]:
|
906
1012
|
"""Build a :obj:`.SwitchCaseOp` into a :class:`.ast.SwitchStatement`."""
|
907
1013
|
real_target = self.build_expression(expr.lift(instruction.operation.target))
|
908
|
-
|
909
|
-
|
910
|
-
ast.Identifier(self._unique_name("switch_dummy", global_scope)), global_scope
|
1014
|
+
target = self.symbols.register_variable(
|
1015
|
+
"switch_dummy", None, allow_rename=True, force_global=True
|
911
1016
|
)
|
912
1017
|
self._global_classical_forward_declarations.append(
|
913
1018
|
ast.ClassicalDeclaration(ast.IntType(), target, None)
|
@@ -921,9 +1026,8 @@ class QASM3Builder:
|
|
921
1026
|
ast.DefaultCase() if v is CASE_DEFAULT else self.build_integer(v)
|
922
1027
|
for v in values
|
923
1028
|
]
|
924
|
-
self.
|
925
|
-
|
926
|
-
self.pop_scope()
|
1029
|
+
with self.new_scope(case_block, instruction.qubits, instruction.clbits):
|
1030
|
+
case_body = ast.ProgramBlock(self.build_current_scope())
|
927
1031
|
return values, case_body
|
928
1032
|
|
929
1033
|
return [
|
@@ -937,13 +1041,12 @@ class QASM3Builder:
|
|
937
1041
|
),
|
938
1042
|
]
|
939
1043
|
|
940
|
-
# Handle the
|
1044
|
+
# Handle the stabilized syntax.
|
941
1045
|
cases = []
|
942
1046
|
default = None
|
943
1047
|
for values, block in instruction.operation.cases_specifier():
|
944
|
-
self.
|
945
|
-
|
946
|
-
self.pop_scope()
|
1048
|
+
with self.new_scope(block, instruction.qubits, instruction.clbits):
|
1049
|
+
case_body = ast.ProgramBlock(self.build_current_scope())
|
947
1050
|
if CASE_DEFAULT in values:
|
948
1051
|
# Even if it's mixed in with other cases, we can skip them and only output the
|
949
1052
|
# `default` since that's valid and execution will be the same; the evaluation of
|
@@ -961,39 +1064,34 @@ class QASM3Builder:
|
|
961
1064
|
"""Build a :obj:`.WhileLoopOp` into a :obj:`.ast.WhileLoopStatement`."""
|
962
1065
|
condition = self.build_expression(_lift_condition(instruction.operation.condition))
|
963
1066
|
loop_circuit = instruction.operation.blocks[0]
|
964
|
-
self.
|
965
|
-
|
966
|
-
self.pop_scope()
|
1067
|
+
with self.new_scope(loop_circuit, instruction.qubits, instruction.clbits):
|
1068
|
+
loop_body = ast.ProgramBlock(self.build_current_scope())
|
967
1069
|
return ast.WhileLoopStatement(condition, loop_body)
|
968
1070
|
|
969
1071
|
def build_for_loop(self, instruction: CircuitInstruction) -> ast.ForLoopStatement:
|
970
1072
|
"""Build a :obj:`.ForLoopOp` into a :obj:`.ast.ForLoopStatement`."""
|
971
1073
|
indexset, loop_parameter, loop_circuit = instruction.operation.params
|
972
|
-
self.
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
# _infer_parameter_declaration), so it doesn't matter that we haven't declared this.
|
977
|
-
loop_parameter_ast = self._reserve_variable_name(ast.Identifier("_"), scope)
|
978
|
-
else:
|
979
|
-
loop_parameter_ast = self._register_variable(loop_parameter, scope)
|
980
|
-
if isinstance(indexset, range):
|
981
|
-
# OpenQASM 3 uses inclusive ranges on both ends, unlike Python.
|
982
|
-
indexset_ast = ast.Range(
|
983
|
-
start=self.build_integer(indexset.start),
|
984
|
-
end=self.build_integer(indexset.stop - 1),
|
985
|
-
step=self.build_integer(indexset.step) if indexset.step != 1 else None,
|
1074
|
+
with self.new_scope(loop_circuit, instruction.qubits, instruction.clbits):
|
1075
|
+
name = "_" if loop_parameter is None else loop_parameter.name
|
1076
|
+
loop_parameter_ast = self.symbols.register_variable(
|
1077
|
+
name, loop_parameter, allow_rename=True
|
986
1078
|
)
|
987
|
-
|
988
|
-
|
989
|
-
indexset_ast = ast.
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
1079
|
+
if isinstance(indexset, range):
|
1080
|
+
# OpenQASM 3 uses inclusive ranges on both ends, unlike Python.
|
1081
|
+
indexset_ast = ast.Range(
|
1082
|
+
start=self.build_integer(indexset.start),
|
1083
|
+
end=self.build_integer(indexset.stop - 1),
|
1084
|
+
step=self.build_integer(indexset.step) if indexset.step != 1 else None,
|
1085
|
+
)
|
1086
|
+
else:
|
1087
|
+
try:
|
1088
|
+
indexset_ast = ast.IndexSet([self.build_integer(value) for value in indexset])
|
1089
|
+
except QASM3ExporterError:
|
1090
|
+
raise QASM3ExporterError(
|
1091
|
+
"The values in OpenQASM 3 'for' loops must all be integers, but received"
|
1092
|
+
f" '{indexset}'."
|
1093
|
+
) from None
|
1094
|
+
body_ast = ast.ProgramBlock(self.build_current_scope())
|
997
1095
|
return ast.ForLoopStatement(indexset_ast, loop_parameter_ast, body_ast)
|
998
1096
|
|
999
1097
|
def build_expression(self, node: expr.Expr) -> ast.Expression:
|
@@ -1048,11 +1146,13 @@ class QASM3Builder:
|
|
1048
1146
|
)
|
1049
1147
|
|
1050
1148
|
def build_gate_call(self, instruction: CircuitInstruction):
|
1051
|
-
"""Builds a
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1149
|
+
"""Builds a gate-call AST node.
|
1150
|
+
|
1151
|
+
This will also push the gate into the symbol table (if required), including recursively
|
1152
|
+
defining the gate blocks."""
|
1153
|
+
ident = self.symbols.get_gate(instruction.operation)
|
1154
|
+
if ident is None:
|
1155
|
+
ident = self.define_gate(instruction.operation)
|
1056
1156
|
qubits = [self._lookup_variable(qubit) for qubit in instruction.qubits]
|
1057
1157
|
if self.disable_constants:
|
1058
1158
|
parameters = [
|
@@ -1065,7 +1165,7 @@ class QASM3Builder:
|
|
1065
1165
|
for param in instruction.operation.params
|
1066
1166
|
]
|
1067
1167
|
|
1068
|
-
return ast.QuantumGateCall(
|
1168
|
+
return ast.QuantumGateCall(ident, qubits, parameters=parameters)
|
1069
1169
|
|
1070
1170
|
|
1071
1171
|
def _infer_variable_declaration(
|
@@ -1102,7 +1202,10 @@ def _infer_variable_declaration(
|
|
1102
1202
|
# _should_ be an intrinsic part of the parameter, or somewhere publicly accessible, but
|
1103
1203
|
# Terra doesn't have those concepts yet. We can only try and guess at the type by looking
|
1104
1204
|
# at all the places it's used in the circuit.
|
1105
|
-
for
|
1205
|
+
for instr_index, index in circuit._data._raw_parameter_table_entry(parameter):
|
1206
|
+
if instr_index is None:
|
1207
|
+
continue
|
1208
|
+
instruction = circuit.data[instr_index].operation
|
1106
1209
|
if isinstance(instruction, ForLoopOp):
|
1107
1210
|
# The parameters of ForLoopOp are (indexset, loop_parameter, body).
|
1108
1211
|
if index == 1:
|